mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
Merge branch 'master' into fix_reset
This commit is contained in:
commit
8c0c290102
@ -66,6 +66,7 @@ module.exports = {
|
||||
'no-alert': isProduction ? 'error' : 'warn',
|
||||
'no-console': isProduction ? 'error' : 'warn',
|
||||
'no-debugger': isProduction ? 'error' : 'warn',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
|
17
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Bug反馈
|
||||
about: 'Bug report'
|
||||
title: "[Bug report]"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**问题描述**
|
||||
请尽量简洁、完整地描述你遇到的bug,至少包括以下部分(如有必要,可提供截图):
|
||||
1. 问题触发的条件/操作流程
|
||||
2. 期望表现与实际表现
|
||||
|
||||
**环境信息**
|
||||
至少包含以下部分:
|
||||
1. 系统环境(Mac or Windows)
|
||||
2. 浏览器环境(如:Chrome v89.0.4389.114)
|
28
.github/workflows/deploy.yml
vendored
Normal file
28
.github/workflows/deploy.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
name: deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js v14.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '14.x'
|
||||
|
||||
- name: Install and Build
|
||||
run: |
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
- name: Deploy to github pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.PPTIST }}
|
||||
commit_message: deploy to github pages
|
||||
publish_dir: ./dist
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,6 +1,6 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
|
100
README.md
100
README.md
@ -1,11 +1,11 @@
|
||||
# 🎨 PPTist
|
||||
> 一个基于 Vue3.x + TypeScript 的在线演示文稿应用,还原了大部分PPT常用功能,支持 文字、图片、形状、线条、图表、表格 6种最常用的元素类型,每一种元素都拥有高度可编辑能力,同时支持丰富的快捷键和右键菜单,尽可能还原本地桌面应用的使用体验。
|
||||
|
||||
在线体验地址:https://pipipi-pikachu.github.io/PPTist/
|
||||
在线体验地址:[https://pipipi-pikachu.github.io/PPTist/](https://pipipi-pikachu.github.io/PPTist/)
|
||||
|
||||
如果网络状态不佳,可以访问国内镜像:https://pptist.gitee.io/
|
||||
如果网络状态不佳,可以访问国内镜像:[https://pptist.gitee.io/](https://pptist.gitee.io/)
|
||||
|
||||
Github仓库:https://github.com/pipipi-pikachu/PPTist
|
||||
Github仓库:[https://github.com/pipipi-pikachu/PPTist](https://github.com/pipipi-pikachu/PPTist)
|
||||
|
||||
|
||||
# 🚀 项目运行
|
||||
@ -17,21 +17,20 @@ npm run serve
|
||||
|
||||
|
||||
# 📚 功能列表
|
||||
## 通用功能
|
||||
- 历史记录
|
||||
### 通用功能
|
||||
- 历史记录(撤销、重做)
|
||||
- 快捷键
|
||||
- 右键菜单
|
||||
|
||||
## 幻灯片页面编辑
|
||||
### 幻灯片页面编辑
|
||||
- 页面添加、删除
|
||||
- 页面顺序调整
|
||||
- 页面复制粘贴
|
||||
- 背景设置(纯色、渐变、图片)
|
||||
- 网格线
|
||||
- 画布缩放
|
||||
- 主题设置
|
||||
- 幻灯片备注
|
||||
|
||||
## 幻灯片元素编辑
|
||||
### 幻灯片元素编辑
|
||||
- 元素添加、删除
|
||||
- 元素复制粘贴
|
||||
- 元素拖拽移动
|
||||
@ -44,11 +43,11 @@ npm run serve
|
||||
- 元素层级调整
|
||||
- 元素对齐到画布
|
||||
- 元素对齐到其他元素
|
||||
- 多元素均匀分布
|
||||
- 拖拽添加图文
|
||||
- 粘贴外部图片
|
||||
- 元素坐标、尺寸和旋转角度设置
|
||||
|
||||
### 文字
|
||||
#### 文字
|
||||
- 富文本编辑(颜色、高亮、字体、字号、加粗、斜体、下划线、删除线、角标、行内代码、引用、对齐方式、项目符号、清除格式)
|
||||
- 行高
|
||||
- 字间距
|
||||
@ -56,7 +55,7 @@ npm run serve
|
||||
- 边框
|
||||
- 阴影
|
||||
- 透明度
|
||||
### 图片
|
||||
#### 图片
|
||||
- 裁剪(自定义、按形状、按纵横比)
|
||||
- 滤镜
|
||||
- 翻转
|
||||
@ -65,43 +64,37 @@ npm run serve
|
||||
- 替换图片
|
||||
- 重置图片
|
||||
- 设置为背景图
|
||||
### 形状
|
||||
#### 形状
|
||||
- 填充色
|
||||
- 边框
|
||||
- 阴影
|
||||
- 透明度
|
||||
- 翻转
|
||||
### 线条
|
||||
#### 线条
|
||||
- 颜色
|
||||
- 宽度
|
||||
- 样式
|
||||
- 端点样式
|
||||
### 图表(柱状图、折线图、饼图)
|
||||
#### 图表(柱状图、折线图、饼图)
|
||||
- 数据编辑
|
||||
- 背景填充
|
||||
- 主题色
|
||||
- 坐标系与坐标文字颜色
|
||||
- 其他设置(柱状图转条形图、折线图转面积图、折线图转散点图、饼图转环形图、折线图开关平滑曲线)
|
||||
- 边框
|
||||
### 表格
|
||||
#### 表格
|
||||
- 行、列添加删除
|
||||
- 行列数设置
|
||||
- 主题设置(主题色、表头、汇总行、第一列、最后一列)
|
||||
- 合并单元格
|
||||
- 单元格样式(填充色、文字颜色、加粗、斜体、下划线、删除线、对齐方式)
|
||||
- 边框
|
||||
## 幻灯片放映
|
||||
### 幻灯片放映
|
||||
- 翻页动画
|
||||
- 元素入场动画
|
||||
- 全部幻灯片预览
|
||||
- 画笔工具
|
||||
|
||||
# 📃 TODO
|
||||
- [ ] 幻灯片模板
|
||||
- [ ] 图表缩略图优化
|
||||
- [ ] 公式元素
|
||||
- [ ] 导出(PPT、PDF、图片、JSON)
|
||||
- [ ] 导入(PPT)
|
||||
|
||||
# 💡 常见问题
|
||||
**Q. 为什么xxx快捷键没有作用?**
|
||||
@ -128,32 +121,55 @@ A. 由于本演示项目不依赖后端,插入本地图片实际引用的是Ba
|
||||
|
||||
A. 设置预置主题的作用是使新添加的元素和页面应用主题样式,不会对已有的元素和页面生效(字体颜色除外),您可以使用“应用主题到全部”功能,将当前主题应用到当前全部页面中。
|
||||
|
||||
**Q. 设置在线字体不生效?**
|
||||
|
||||
# 🔧 项目依赖
|
||||
A. 设置在线字体时会下载对应的字体文件,该文件较大,需要等待下载完成后才会应用新的字体。
|
||||
|
||||
`ant-design-vue` -- UI库
|
||||
|
||||
`lodash` -- 工具库
|
||||
# 📁 项目目录结构
|
||||
```
|
||||
├── assets // 静态资源
|
||||
│ ├── fonts // 在线字体文件
|
||||
│ └── styles // 样式
|
||||
│ ├── antd.scss // antd默认样式覆盖
|
||||
│ ├── font.scss // 在线字体定义
|
||||
│ ├── global.scss // 通用全局样式
|
||||
│ ├── mixin.scss // scss全局混入
|
||||
│ ├── variable.scss // scss全局变量
|
||||
│ └── prosemirror.scss // ProseMirror 富文本默认样式
|
||||
├── components // 与业务逻辑无关的通用组件
|
||||
├── configs // 配置文件,如:画布尺寸、字体、动画配置、快捷键配置、预置形状、预置线条等数据。
|
||||
├── hooks // 供多个组件(模块)使用的 hooks 方法
|
||||
├── mocks // mocks 数据
|
||||
├── plugins // 自定义的 Vue 插件
|
||||
├── prosemirror // ProseMirror 富文本编辑器相关的文件
|
||||
├── types // 类型定义文件
|
||||
├── store // Vuex store,参考:https://vuex.vuejs.org/zh/guide/
|
||||
├── utils // 通用的工具方法
|
||||
└── views // 业务组件目录,分为 `编辑器` 和 `播放器` 两个部分。
|
||||
├── components // 公用的业务组件
|
||||
├── Editor // 编辑器模块
|
||||
└── Screen // 播放器模块
|
||||
```
|
||||
|
||||
`prosemirror` -- 富文本编辑框架,用于文本元素的富文本编辑
|
||||
|
||||
`chartist` -- svg图表库,用于图表元素
|
||||
# 💿 数据
|
||||
幻灯片的数据主要由 `slides` 和 `theme` 两部分组成。
|
||||
> 换句话说,在实际的生产环境中,一般只需要存储这两项数据即可。
|
||||
|
||||
`tinycolor2` -- 颜色处理工具
|
||||
- `slides` 表示幻灯片页面数据,包括每一页的ID、元素内容、备注、背景、动画、切页方式等信息
|
||||
- `theme` 表示幻灯片主题数据,包括背景色、主题色、字体颜色、字体等信息
|
||||
|
||||
`dexie` -- indexedDB 包装器,用于记录历史操作
|
||||
具体类型的定义可见:[https://github.com/pipipi-pikachu/PPTist/blob/master/src/types/slides.ts](https://github.com/pipipi-pikachu/PPTist/blob/master/src/types/slides.ts)
|
||||
|
||||
`mitt` -- 自定义事件发射/监听
|
||||
|
||||
`animate.css` -- CSS动画库
|
||||
# 📃 TODO
|
||||
- [ ] 幻灯片模板
|
||||
- [ ] 图表缩略图优化
|
||||
- [ ] 公式元素
|
||||
- [ ] 导出、导入
|
||||
|
||||
`vuedraggable` -- 基于vue的拖拽插件,用于调整页面顺序等
|
||||
|
||||
`crypto-js` -- 加密函数库,用于加解密剪贴板内容
|
||||
|
||||
`clipboard` -- 用于复制内容到剪贴板
|
||||
|
||||
`icon-park` -- 图标库
|
||||
> 作为一个在线幻灯片,导出、导入PPTX文件是非常重要的功能。但是经过本人一段时间的调研发现,该功能实现起来的复杂度远超过了预期。由于个人时间有限,暂时可能无法集中精力来做该功能,短时间内还是会更多优先去提升其他方面的使用体验。如果有做过相关内容的朋友,也欢迎给我提建议。
|
||||
|
||||
|
||||
# 💻 贡献代码
|
||||
@ -169,12 +185,12 @@ A. 设置预置主题的作用是使新添加的元素和页面应用主题样
|
||||
- 对于较大的新功能,你需要先提交 Issues,例如 ‘添加 XXX 功能’,确认该功能有被添加的必要后,再开始工作;
|
||||
- 其他如简单的体验或代码优化、文档修正等,只要修改合理都会被接受。
|
||||
|
||||
### 最后,感谢每一位贡献者:
|
||||
- [lhyUnited](https://github.com/lhyUnited)
|
||||
在此,感谢每一位贡献者。
|
||||
|
||||
|
||||
# 📄 开源协议
|
||||
[MIT License](https://github.com/pipipi-pikachu/PPTist/blob/master/LICENSE)
|
||||
|
||||
|
||||
# 💣 友情提示
|
||||
本项目不接受任何形式的私人咨询,有任何问题欢迎在 github 提交你的 Issues
|
||||
本项目不接受任何形式的私人咨询,有任何问题 [欢迎在 github 提交你的 Issues](https://github.com/pipipi-pikachu/PPTist/issues)
|
1
dist/css/app.abb643bc.css
vendored
1
dist/css/app.abb643bc.css
vendored
File diff suppressed because one or more lines are too long
7
dist/css/chunk-vendors.9281d9df.css
vendored
7
dist/css/chunk-vendors.9281d9df.css
vendored
File diff suppressed because one or more lines are too long
BIN
dist/favicon.ico
vendored
BIN
dist/favicon.ico
vendored
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB |
BIN
dist/fonts/仓耳小丸子.676e187a.ttf
vendored
BIN
dist/fonts/仓耳小丸子.676e187a.ttf
vendored
Binary file not shown.
BIN
dist/fonts/优设标题黑.1726685c.ttf
vendored
BIN
dist/fonts/优设标题黑.1726685c.ttf
vendored
Binary file not shown.
BIN
dist/fonts/峰广明锐体.8bdb14f7.ttf
vendored
BIN
dist/fonts/峰广明锐体.8bdb14f7.ttf
vendored
Binary file not shown.
BIN
dist/fonts/摄图摩登小方体.de722238.ttf
vendored
BIN
dist/fonts/摄图摩登小方体.de722238.ttf
vendored
Binary file not shown.
BIN
dist/fonts/站酷快乐体.0aceab97.ttf
vendored
BIN
dist/fonts/站酷快乐体.0aceab97.ttf
vendored
Binary file not shown.
BIN
dist/fonts/站酷酷黑体.6b4f114c.ttf
vendored
BIN
dist/fonts/站酷酷黑体.6b4f114c.ttf
vendored
Binary file not shown.
BIN
dist/fonts/素材集市康康体.8db9d61f.ttf
vendored
BIN
dist/fonts/素材集市康康体.8db9d61f.ttf
vendored
Binary file not shown.
BIN
dist/fonts/联盟起艺卢帅正锐黑体.42cb84f9.ttf
vendored
BIN
dist/fonts/联盟起艺卢帅正锐黑体.42cb84f9.ttf
vendored
Binary file not shown.
BIN
dist/fonts/谦度手写楷体.7bfb15ee.ttf
vendored
BIN
dist/fonts/谦度手写楷体.7bfb15ee.ttf
vendored
Binary file not shown.
BIN
dist/fonts/途牛类圆体.abaea3c4.ttf
vendored
BIN
dist/fonts/途牛类圆体.abaea3c4.ttf
vendored
Binary file not shown.
BIN
dist/fonts/锐字真言体.1583afec.ttf
vendored
BIN
dist/fonts/锐字真言体.1583afec.ttf
vendored
Binary file not shown.
BIN
dist/fonts/问藏书房.59a94370.ttf
vendored
BIN
dist/fonts/问藏书房.59a94370.ttf
vendored
Binary file not shown.
1
dist/index.html
vendored
1
dist/index.html
vendored
@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta name="renderer" content="webkit"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>PPTIST - 在线演示文稿</title><link href="css/app.abb643bc.css" rel="preload" as="style"><link href="css/chunk-vendors.9281d9df.css" rel="preload" as="style"><link href="js/app.42e95de6.js" rel="preload" as="script"><link href="js/chunk-vendors.cdb8e65d.js" rel="preload" as="script"><link href="css/chunk-vendors.9281d9df.css" rel="stylesheet"><link href="css/app.abb643bc.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but pptist doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>document.oncontextmenu = e => e.preventDefault()</script><script src="js/chunk-vendors.cdb8e65d.js"></script><script src="js/app.42e95de6.js"></script></body></html>
|
2
dist/js/app.42e95de6.js
vendored
2
dist/js/app.42e95de6.js
vendored
File diff suppressed because one or more lines are too long
1
dist/js/app.42e95de6.js.map
vendored
1
dist/js/app.42e95de6.js.map
vendored
File diff suppressed because one or more lines are too long
326
dist/js/chunk-vendors.cdb8e65d.js
vendored
326
dist/js/chunk-vendors.cdb8e65d.js
vendored
File diff suppressed because one or more lines are too long
1
dist/js/chunk-vendors.cdb8e65d.js.map
vendored
1
dist/js/chunk-vendors.cdb8e65d.js.map
vendored
File diff suppressed because one or more lines are too long
496
package-lock.json
generated
496
package-lock.json
generated
@ -6,8 +6,8 @@
|
||||
"dependencies": {
|
||||
"@ant-design-vue/use": {
|
||||
"version": "0.0.1-alpha.9",
|
||||
"resolved": "https://registry.npm.taobao.org/@ant-design-vue/use/download/@ant-design-vue/use-0.0.1-alpha.9.tgz",
|
||||
"integrity": "sha1-t98mFQRpORODuhwt1LolAmelhBE=",
|
||||
"resolved": "https://registry.npmjs.org/@ant-design-vue/use/-/use-0.0.1-alpha.9.tgz",
|
||||
"integrity": "sha512-X+ESJt+e95sRwlSkpzETjc0opE5l34tCjMEm92JkoM4BVl6YxG9IgyX1dBy0W2jF74SSCOiCc7GdIlsPFQvE/g==",
|
||||
"requires": {
|
||||
"async-validator": "^3.4.0",
|
||||
"lodash-es": "^4.17.15",
|
||||
@ -17,21 +17,21 @@
|
||||
},
|
||||
"@ant-design/colors": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npm.taobao.org/@ant-design/colors/download/@ant-design/colors-5.1.1.tgz",
|
||||
"integrity": "sha1-gAshhrHifmZDLmfQPtlq8+IdiUA=",
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-5.1.1.tgz",
|
||||
"integrity": "sha512-Txy4KpHrp3q4XZdfgOBqLl+lkQIc3tEvHXOimRN1giX1AEC7mGtyrO9p8iRGJ3FLuVMGa2gNEzQyghVymLttKQ==",
|
||||
"requires": {
|
||||
"@ctrl/tinycolor": "^3.3.1"
|
||||
}
|
||||
},
|
||||
"@ant-design/icons-svg": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npm.taobao.org/@ant-design/icons-svg/download/@ant-design/icons-svg-4.1.0.tgz",
|
||||
"integrity": "sha1-SAsCX0sg73/o9H1KSEbk/uhOoGw="
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.1.0.tgz",
|
||||
"integrity": "sha512-Fi03PfuUqRs76aI3UWYpP864lkrfPo0hluwGqh7NJdLhvH4iRDc3jbJqZIvRDLHKbXrvAfPPV3+zjUccfFvWOQ=="
|
||||
},
|
||||
"@ant-design/icons-vue": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npm.taobao.org/@ant-design/icons-vue/download/@ant-design/icons-vue-6.0.1.tgz",
|
||||
"integrity": "sha1-nYBMPHTSz6+XyxjlgtO5QAk09f0=",
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/icons-vue/-/icons-vue-6.0.1.tgz",
|
||||
"integrity": "sha512-HigIgEVV6bbcrz2A92/qDzi/aKWB5EC6b6E1mxMB6aQA7ksiKY+gi4U94TpqyEIIhR23uaDrjufJ+xCZQ+vx6Q==",
|
||||
"requires": {
|
||||
"@ant-design/colors": "^5.0.0",
|
||||
"@ant-design/icons-svg": "^4.0.0",
|
||||
@ -1412,8 +1412,8 @@
|
||||
},
|
||||
"@ctrl/tinycolor": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npm.taobao.org/@ctrl/tinycolor/download/@ctrl/tinycolor-3.4.0.tgz",
|
||||
"integrity": "sha1-w8WuVDyJfKqcKmhjC+01W+X5mQ8="
|
||||
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz",
|
||||
"integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ=="
|
||||
},
|
||||
"@hapi/address": {
|
||||
"version": "2.1.4",
|
||||
@ -1912,12 +1912,19 @@
|
||||
}
|
||||
},
|
||||
"@simonwep/pickr": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npm.taobao.org/@simonwep/pickr/download/@simonwep/pickr-1.8.0.tgz?cache=0&sync_timestamp=1607168186154&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40simonwep%2Fpickr%2Fdownload%2F%40simonwep%2Fpickr-1.8.0.tgz",
|
||||
"integrity": "sha1-rb/5pPfw5Z3smUZQjF5IG3q64Pg=",
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@simonwep/pickr/-/pickr-1.8.1.tgz",
|
||||
"integrity": "sha512-3Q5+INWW0Py+/E9hgy0cyD0/0w/yGZbkxam6RzFVFDOEHgAqMVJR+x9znx58/ky/ZIvE/78FbH189yIC9h111A==",
|
||||
"requires": {
|
||||
"core-js": "^3.8.0",
|
||||
"core-js": "^3.12.1",
|
||||
"nanopop": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "3.12.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.12.1.tgz",
|
||||
"integrity": "sha512-Ne9DKPHTObRuB09Dru5AjwKjY4cJHVGu+y5f7coGn1E9Grkc3p2iBwE9AI/nJzsE29mQF7oq+mhYYRqOMFN1Bw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@soda/friendly-errors-webpack-plugin": {
|
||||
@ -2153,12 +2160,6 @@
|
||||
"integrity": "sha1-OkvSRRiw5sWUDaTiZZ7rLvCAaWM=",
|
||||
"dev": true
|
||||
},
|
||||
"@types/eslint-visitor-keys": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npm.taobao.org/@types/eslint-visitor-keys/download/@types/eslint-visitor-keys-1.0.0.tgz?cache=0&sync_timestamp=1613379039201&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Feslint-visitor-keys%2Fdownload%2F%40types%2Feslint-visitor-keys-1.0.0.tgz",
|
||||
"integrity": "sha1-HuMNeVRMqE1o1LPNsK9PIFZj3S0=",
|
||||
"dev": true
|
||||
},
|
||||
"@types/express": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npm.taobao.org/@types/express/download/@types/express-4.17.11.tgz?cache=0&sync_timestamp=1613378518678&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fexpress%2Fdownload%2F%40types%2Fexpress-4.17.11.tgz",
|
||||
@ -2248,9 +2249,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.168",
|
||||
"resolved": "https://registry.npm.taobao.org/@types/lodash/download/@types/lodash-4.14.168.tgz?cache=0&sync_timestamp=1613379222224&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Flodash%2Fdownload%2F%40types%2Flodash-4.14.168.tgz",
|
||||
"integrity": "sha1-/iRjLnm3rePxMoka//hsql5c4Ag="
|
||||
"version": "4.14.169",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.169.tgz",
|
||||
"integrity": "sha512-DvmZHoHTFJ8zhVYwCLWbQ7uAbYQEk52Ev2/ZiQ7Y7gQGeV9pjBqjnQpECMHfKS1rCYAhMI7LHVxwyZLZinJgdw=="
|
||||
},
|
||||
"@types/mdast": {
|
||||
"version": "3.0.3",
|
||||
@ -2578,54 +2579,337 @@
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"version": "2.34.0",
|
||||
"resolved": "https://registry.npm.taobao.org/@typescript-eslint/eslint-plugin/download/@typescript-eslint/eslint-plugin-2.34.0.tgz?cache=0&sync_timestamp=1616464424176&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40typescript-eslint%2Feslint-plugin%2Fdownload%2F%40typescript-eslint%2Feslint-plugin-2.34.0.tgz",
|
||||
"integrity": "sha1-b4zopGx96kpvHRcdK7j7rm2sK+k=",
|
||||
"version": "4.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.23.0.tgz",
|
||||
"integrity": "sha512-tGK1y3KIvdsQEEgq6xNn1DjiFJtl+wn8JJQiETtCbdQxw1vzjXyAaIkEmO2l6Nq24iy3uZBMFQjZ6ECf1QdgGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/experimental-utils": "2.34.0",
|
||||
"@typescript-eslint/experimental-utils": "4.23.0",
|
||||
"@typescript-eslint/scope-manager": "4.23.0",
|
||||
"debug": "^4.1.1",
|
||||
"functional-red-black-tree": "^1.0.1",
|
||||
"lodash": "^4.17.15",
|
||||
"regexpp": "^3.0.0",
|
||||
"semver": "^7.3.2",
|
||||
"tsutils": "^3.17.1"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/experimental-utils": {
|
||||
"version": "2.34.0",
|
||||
"resolved": "https://registry.npm.taobao.org/@typescript-eslint/experimental-utils/download/@typescript-eslint/experimental-utils-2.34.0.tgz?cache=0&sync_timestamp=1616464293813&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40typescript-eslint%2Fexperimental-utils%2Fdownload%2F%40typescript-eslint%2Fexperimental-utils-2.34.0.tgz",
|
||||
"integrity": "sha1-01JLZEzbQO687KZ/jPPkzJyPmA8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.3",
|
||||
"@typescript-eslint/typescript-estree": "2.34.0",
|
||||
"eslint-scope": "^5.0.0",
|
||||
"eslint-utils": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz",
|
||||
"integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==",
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/experimental-utils": {
|
||||
"version": "4.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.23.0.tgz",
|
||||
"integrity": "sha512-WAFNiTDnQfrF3Z2fQ05nmCgPsO5o790vOhmWKXbbYQTO9erE1/YsFot5/LnOUizLzU2eeuz6+U/81KV5/hFTGA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.3",
|
||||
"@typescript-eslint/scope-manager": "4.23.0",
|
||||
"@typescript-eslint/types": "4.23.0",
|
||||
"@typescript-eslint/typescript-estree": "4.23.0",
|
||||
"eslint-scope": "^5.0.0",
|
||||
"eslint-utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "4.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.23.0.tgz",
|
||||
"integrity": "sha512-5Sty6zPEVZF5fbvrZczfmLCOcby3sfrSPu30qKoY1U3mca5/jvU5cwsPb/CO6Q3ByRjixTMIVsDkqwIxCf/dMw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "4.23.0",
|
||||
"@typescript-eslint/visitor-keys": "4.23.0",
|
||||
"debug": "^4.1.1",
|
||||
"globby": "^11.0.1",
|
||||
"is-glob": "^4.0.1",
|
||||
"semver": "^7.3.2",
|
||||
"tsutils": "^3.17.1"
|
||||
}
|
||||
},
|
||||
"array-union": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
|
||||
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
|
||||
"dev": true
|
||||
},
|
||||
"braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fill-range": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"dir-glob": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-type": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"fast-glob": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz",
|
||||
"integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@nodelib/fs.stat": "^2.0.2",
|
||||
"@nodelib/fs.walk": "^1.2.3",
|
||||
"glob-parent": "^5.1.0",
|
||||
"merge2": "^1.3.0",
|
||||
"micromatch": "^4.0.2",
|
||||
"picomatch": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"globby": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz",
|
||||
"integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-union": "^2.1.0",
|
||||
"dir-glob": "^3.0.1",
|
||||
"fast-glob": "^3.1.1",
|
||||
"ignore": "^5.1.4",
|
||||
"merge2": "^1.3.0",
|
||||
"slash": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"ignore": {
|
||||
"version": "5.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
|
||||
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
|
||||
"dev": true
|
||||
},
|
||||
"is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
|
||||
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"braces": "^3.0.1",
|
||||
"picomatch": "^2.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"picomatch": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz",
|
||||
"integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/parser": {
|
||||
"version": "2.34.0",
|
||||
"resolved": "https://registry.npm.taobao.org/@typescript-eslint/parser/download/@typescript-eslint/parser-2.34.0.tgz",
|
||||
"integrity": "sha1-UCUmMMoxloVCDpo5ygX+GFola8g=",
|
||||
"version": "4.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.23.0.tgz",
|
||||
"integrity": "sha512-wsvjksHBMOqySy/Pi2Q6UuIuHYbgAMwLczRl4YanEPKW5KVxI9ZzDYh3B5DtcZPQTGRWFJrfcbJ6L01Leybwug==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/eslint-visitor-keys": "^1.0.0",
|
||||
"@typescript-eslint/experimental-utils": "2.34.0",
|
||||
"@typescript-eslint/typescript-estree": "2.34.0",
|
||||
"eslint-visitor-keys": "^1.1.0"
|
||||
"@typescript-eslint/scope-manager": "4.23.0",
|
||||
"@typescript-eslint/types": "4.23.0",
|
||||
"@typescript-eslint/typescript-estree": "4.23.0",
|
||||
"debug": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "2.34.0",
|
||||
"resolved": "https://registry.npm.taobao.org/@typescript-eslint/typescript-estree/download/@typescript-eslint/typescript-estree-2.34.0.tgz",
|
||||
"integrity": "sha1-FK62NTs57wcyzH8bgoUpSTfPN9U=",
|
||||
"@typescript-eslint/scope-manager": {
|
||||
"version": "4.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.23.0.tgz",
|
||||
"integrity": "sha512-ZZ21PCFxPhI3n0wuqEJK9omkw51wi2bmeKJvlRZPH5YFkcawKOuRMQMnI8mH6Vo0/DoHSeZJnHiIx84LmVQY+w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "4.23.0",
|
||||
"@typescript-eslint/visitor-keys": "4.23.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/types": {
|
||||
"version": "4.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.23.0.tgz",
|
||||
"integrity": "sha512-oqkNWyG2SLS7uTWLZf6Sr7Dm02gA5yxiz1RP87tvsmDsguVATdpVguHr4HoGOcFOpCvx9vtCSCyQUGfzq28YCw==",
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "4.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.23.0.tgz",
|
||||
"integrity": "sha512-5Sty6zPEVZF5fbvrZczfmLCOcby3sfrSPu30qKoY1U3mca5/jvU5cwsPb/CO6Q3ByRjixTMIVsDkqwIxCf/dMw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "4.23.0",
|
||||
"@typescript-eslint/visitor-keys": "4.23.0",
|
||||
"debug": "^4.1.1",
|
||||
"eslint-visitor-keys": "^1.1.0",
|
||||
"glob": "^7.1.6",
|
||||
"globby": "^11.0.1",
|
||||
"is-glob": "^4.0.1",
|
||||
"lodash": "^4.17.15",
|
||||
"semver": "^7.3.2",
|
||||
"tsutils": "^3.17.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz",
|
||||
"integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==",
|
||||
"dev": true
|
||||
},
|
||||
"array-union": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
|
||||
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
|
||||
"dev": true
|
||||
},
|
||||
"braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fill-range": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"dir-glob": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-type": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"fast-glob": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz",
|
||||
"integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@nodelib/fs.stat": "^2.0.2",
|
||||
"@nodelib/fs.walk": "^1.2.3",
|
||||
"glob-parent": "^5.1.0",
|
||||
"merge2": "^1.3.0",
|
||||
"micromatch": "^4.0.2",
|
||||
"picomatch": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"globby": {
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz",
|
||||
"integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-union": "^2.1.0",
|
||||
"dir-glob": "^3.0.1",
|
||||
"fast-glob": "^3.1.1",
|
||||
"ignore": "^5.1.4",
|
||||
"merge2": "^1.3.0",
|
||||
"slash": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"ignore": {
|
||||
"version": "5.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
|
||||
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
|
||||
"dev": true
|
||||
},
|
||||
"is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
|
||||
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"braces": "^3.0.1",
|
||||
"picomatch": "^2.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"picomatch": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz",
|
||||
"integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/visitor-keys": {
|
||||
"version": "4.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.23.0.tgz",
|
||||
"integrity": "sha512-5PNe5cmX9pSifit0H+nPoQBXdbNzi5tOEec+3riK+ku4e3er37pKxMKDH5Ct5Y4fhWxcD4spnlYjxi9vXbSpwg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/types": "4.23.0",
|
||||
"eslint-visitor-keys": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint-visitor-keys": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
|
||||
"integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@vue/babel-helper-vue-jsx-merge-props": {
|
||||
@ -3384,9 +3668,9 @@
|
||||
}
|
||||
},
|
||||
"@vue/eslint-config-typescript": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npm.taobao.org/@vue/eslint-config-typescript/download/@vue/eslint-config-typescript-5.1.0.tgz",
|
||||
"integrity": "sha1-F+sa9k9j4jH8zspWA4Wb37T11OA=",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-7.0.0.tgz",
|
||||
"integrity": "sha512-UxUlvpSrFOoF8aQ+zX1leYiEBEm7CZmXYn/ZEM1zwSadUzpamx56RB4+Htdjisv1mX2tOjBegNUqH3kz2OL+Aw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"vue-eslint-parser": "^7.0.0"
|
||||
@ -3807,9 +4091,9 @@
|
||||
}
|
||||
},
|
||||
"ant-design-vue": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npm.taobao.org/ant-design-vue/download/ant-design-vue-2.1.2.tgz",
|
||||
"integrity": "sha1-IGXX5jGZwMWEkZRYr1e2oLWX9nc=",
|
||||
"version": "2.1.6",
|
||||
"resolved": "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-2.1.6.tgz",
|
||||
"integrity": "sha512-qICxb6Y4f7QuSuh/jbLhZA9SkUBnP9xYfy/E6yD7+1fg04aAzmRK8oLv8ETuGTrROVdSVeic9v/NS2BXEuuARg==",
|
||||
"requires": {
|
||||
"@ant-design-vue/use": "^0.0.1-0",
|
||||
"@ant-design/icons-vue": "^6.0.0",
|
||||
@ -3819,7 +4103,7 @@
|
||||
"async-validator": "^3.3.0",
|
||||
"dom-align": "^1.10.4",
|
||||
"dom-scroll-into-view": "^2.0.0",
|
||||
"is-mobile": "^2.2.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.15",
|
||||
"moment": "^2.27.0",
|
||||
"omit.js": "^2.0.0",
|
||||
@ -3905,8 +4189,8 @@
|
||||
},
|
||||
"array-tree-filter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npm.taobao.org/array-tree-filter/download/array-tree-filter-2.1.0.tgz",
|
||||
"integrity": "sha1-hzrAD+yDdJ8lWsjdCDgUtPYykZA="
|
||||
"resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz",
|
||||
"integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw=="
|
||||
},
|
||||
"array-union": {
|
||||
"version": "1.0.2",
|
||||
@ -4031,9 +4315,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"async-validator": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npm.taobao.org/async-validator/download/async-validator-3.5.1.tgz",
|
||||
"integrity": "sha1-zWK5aIskZfSEIOJ620d2CrG1VZ8="
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-3.5.2.tgz",
|
||||
"integrity": "sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ=="
|
||||
},
|
||||
"asynckit": {
|
||||
"version": "0.4.0",
|
||||
@ -5660,8 +5944,8 @@
|
||||
},
|
||||
"compute-scroll-into-view": {
|
||||
"version": "1.0.17",
|
||||
"resolved": "https://registry.npm.taobao.org/compute-scroll-into-view/download/compute-scroll-into-view-1.0.17.tgz?cache=0&sync_timestamp=1614042424875&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcompute-scroll-into-view%2Fdownload%2Fcompute-scroll-into-view-1.0.17.tgz",
|
||||
"integrity": "sha1-aojxis2dQunPS6pr7H4FImB6t6s="
|
||||
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz",
|
||||
"integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg=="
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
@ -6929,9 +7213,9 @@
|
||||
}
|
||||
},
|
||||
"dom-align": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npm.taobao.org/dom-align/download/dom-align-1.12.0.tgz",
|
||||
"integrity": "sha1-VvtxVt8LkQmYMDZNLUj4iWP1opw="
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.1.tgz",
|
||||
"integrity": "sha512-CdTD9EdA5WviP8oO3n+okOm0Xt7dSuWxRTLcJiW0memwUr3Tvz66JDDCh9cb50IZFHXvBmLoyX454uJU/EVg+g=="
|
||||
},
|
||||
"dom-converter": {
|
||||
"version": "0.2.0",
|
||||
@ -6944,8 +7228,8 @@
|
||||
},
|
||||
"dom-scroll-into-view": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npm.taobao.org/dom-scroll-into-view/download/dom-scroll-into-view-2.0.1.tgz",
|
||||
"integrity": "sha1-DezIUigB/Y0/HGujVadNOCxfmJs="
|
||||
"resolved": "https://registry.npmjs.org/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz",
|
||||
"integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w=="
|
||||
},
|
||||
"dom-serializer": {
|
||||
"version": "0.2.2",
|
||||
@ -9116,11 +9400,6 @@
|
||||
"integrity": "sha1-e15vfmZen7QfMAB+2eDUHpf7IUA=",
|
||||
"dev": true
|
||||
},
|
||||
"html-to-image": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.6.0.tgz",
|
||||
"integrity": "sha512-omzkdNnZOacH7udio09I3eGkOrE8VcpzabQbLYmjojpi+FmtofpNY9lHV2fn2DaDO9G9p10O3E8SF5HHgFhSWg=="
|
||||
},
|
||||
"html-webpack-plugin": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npm.taobao.org/html-webpack-plugin/download/html-webpack-plugin-3.2.0.tgz?cache=0&sync_timestamp=1615296080987&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhtml-webpack-plugin%2Fdownload%2Fhtml-webpack-plugin-3.2.0.tgz",
|
||||
@ -9936,11 +10215,6 @@
|
||||
"integrity": "sha1-zDXJdYjaS9Saju3WvECC1E3LI6c=",
|
||||
"dev": true
|
||||
},
|
||||
"is-mobile": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npm.taobao.org/is-mobile/download/is-mobile-2.2.2.tgz",
|
||||
"integrity": "sha1-9snF1Q7gElTOBec5vdg18e1OmVQ="
|
||||
},
|
||||
"is-negative-zero": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npm.taobao.org/is-negative-zero/download/is-negative-zero-2.0.1.tgz?cache=0&sync_timestamp=1607123092746&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-negative-zero%2Fdownload%2Fis-negative-zero-2.0.1.tgz",
|
||||
@ -10011,8 +10285,8 @@
|
||||
},
|
||||
"is-plain-object": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npm.taobao.org/is-plain-object/download/is-plain-object-3.0.1.tgz?cache=0&sync_timestamp=1599667313656&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-plain-object%2Fdownload%2Fis-plain-object-3.0.1.tgz",
|
||||
"integrity": "sha1-Zi2S0kwKpDAkB7DUXSHyJRyF+Fs="
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz",
|
||||
"integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g=="
|
||||
},
|
||||
"is-regex": {
|
||||
"version": "1.1.2",
|
||||
@ -12465,8 +12739,8 @@
|
||||
},
|
||||
"lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npm.taobao.org/lodash-es/download/lodash-es-4.17.21.tgz?cache=0&sync_timestamp=1613836838613&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash-es%2Fdownload%2Flodash-es-4.17.21.tgz",
|
||||
"integrity": "sha1-Q+YmxG5lkbd1C+srUBFzkMYJ4+4="
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||
},
|
||||
"lodash.camelcase": {
|
||||
"version": "4.3.0",
|
||||
@ -13151,8 +13425,8 @@
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.29.1",
|
||||
"resolved": "https://registry.npm.taobao.org/moment/download/moment-2.29.1.tgz?cache=0&sync_timestamp=1601983557585&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmoment%2Fdownload%2Fmoment-2.29.1.tgz",
|
||||
"integrity": "sha1-sr52n6MZQL6e7qZGnAdeNQBvo9M="
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
||||
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
|
||||
},
|
||||
"move-concurrently": {
|
||||
"version": "1.0.1",
|
||||
@ -13241,8 +13515,8 @@
|
||||
},
|
||||
"nanopop": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npm.taobao.org/nanopop/download/nanopop-2.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnanopop%2Fdownload%2Fnanopop-2.1.0.tgz",
|
||||
"integrity": "sha1-I0dlE87iQFiIr9LopLVAZrcLnmA="
|
||||
"resolved": "https://registry.npmjs.org/nanopop/-/nanopop-2.1.0.tgz",
|
||||
"integrity": "sha512-jGTwpFRexSH+fxappnGQtN9dspgE2ipa1aOjtR24igG0pv6JCxImIAmrLRHX+zUF5+1wtsFVbKyfP51kIGAVNw=="
|
||||
},
|
||||
"native-request": {
|
||||
"version": "1.0.8",
|
||||
@ -13653,8 +13927,8 @@
|
||||
},
|
||||
"omit.js": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npm.taobao.org/omit.js/download/omit.js-2.0.2.tgz",
|
||||
"integrity": "sha1-3ZuENvq5R6Xz/yFMslOGMeMT7C8="
|
||||
"resolved": "https://registry.npmjs.org/omit.js/-/omit.js-2.0.2.tgz",
|
||||
"integrity": "sha512-hJmu9D+bNB40YpL9jYebQl4lsTW6yEHRTroJzNLqQJYHm7c+NQnJGfZmIWh8S3q3KoaxV1aLhV6B3+0N0/kyJg=="
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
@ -15591,8 +15865,8 @@
|
||||
},
|
||||
"regexpp": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npm.taobao.org/regexpp/download/regexpp-3.1.0.tgz",
|
||||
"integrity": "sha1-IG0K0KVkjP+9uK5GQ489xRyfeOI=",
|
||||
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
|
||||
"integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"regexpu-core": {
|
||||
@ -15789,8 +16063,8 @@
|
||||
},
|
||||
"resize-observer-polyfill": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npm.taobao.org/resize-observer-polyfill/download/resize-observer-polyfill-1.5.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fresize-observer-polyfill%2Fdownload%2Fresize-observer-polyfill-1.5.1.tgz",
|
||||
"integrity": "sha1-DpAg3T0hAkRY1OvSfiPkAmmBBGQ="
|
||||
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
|
||||
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.20.0",
|
||||
@ -16003,12 +16277,12 @@
|
||||
}
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.32.8",
|
||||
"resolved": "https://registry.npm.taobao.org/sass/download/sass-1.32.8.tgz?cache=0&sync_timestamp=1613687400541&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsass%2Fdownload%2Fsass-1.32.8.tgz",
|
||||
"integrity": "sha1-8WqavY3FMK3Yg05QaHiigIwDe9w=",
|
||||
"version": "1.32.13",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.32.13.tgz",
|
||||
"integrity": "sha512-dEgI9nShraqP7cXQH+lEXVf73WOPCse0QlFzSD8k+1TcOxCMwVXfQlr0jtoluZysQOyJGnfr21dLvYKDJq8HkA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": ">=2.0.0 <4.0.0"
|
||||
"chokidar": ">=3.0.0 <4.0.0"
|
||||
}
|
||||
},
|
||||
"sass-loader": {
|
||||
@ -16060,8 +16334,8 @@
|
||||
},
|
||||
"scroll-into-view-if-needed": {
|
||||
"version": "2.2.28",
|
||||
"resolved": "https://registry.npm.taobao.org/scroll-into-view-if-needed/download/scroll-into-view-if-needed-2.2.28.tgz",
|
||||
"integrity": "sha1-WhWy9YpSZCyIyOylhGROAXA9ZFo=",
|
||||
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.28.tgz",
|
||||
"integrity": "sha512-8LuxJSuFVc92+0AdNv4QOxRL4Abeo1DgLnGNkn1XlaujPH/3cCFz3QI60r2VNu4obJJROzgnIUw5TKQkZvZI1w==",
|
||||
"requires": {
|
||||
"compute-scroll-into-view": "^1.0.17"
|
||||
}
|
||||
@ -16304,8 +16578,8 @@
|
||||
},
|
||||
"shallow-equal": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npm.taobao.org/shallow-equal/download/shallow-equal-1.2.1.tgz",
|
||||
"integrity": "sha1-TBar+lYEOqINBQMk76aJQLDaedo="
|
||||
"resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz",
|
||||
"integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA=="
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "1.2.0",
|
||||
@ -18392,9 +18666,9 @@
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.9.9",
|
||||
"resolved": "https://registry.npm.taobao.org/typescript/download/typescript-3.9.9.tgz?cache=0&sync_timestamp=1616916044038&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftypescript%2Fdownload%2Ftypescript-3.9.9.tgz",
|
||||
"integrity": "sha1-5pkFxUvAaB0FGL1NWHzG8tCxpnQ=",
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
|
||||
"integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==",
|
||||
"dev": true
|
||||
},
|
||||
"uglify-js": {
|
||||
@ -19004,8 +19278,8 @@
|
||||
},
|
||||
"vue-types": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npm.taobao.org/vue-types/download/vue-types-3.0.2.tgz?cache=0&sync_timestamp=1612247108041&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-types%2Fdownload%2Fvue-types-3.0.2.tgz",
|
||||
"integrity": "sha1-7BbgXUEsA4Ji/B76TOuWR+f7YB0=",
|
||||
"resolved": "https://registry.npmjs.org/vue-types/-/vue-types-3.0.2.tgz",
|
||||
"integrity": "sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==",
|
||||
"requires": {
|
||||
"is-plain-object": "3.0.1"
|
||||
}
|
||||
@ -19059,8 +19333,8 @@
|
||||
},
|
||||
"warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npm.taobao.org/warning/download/warning-4.0.3.tgz?cache=0&sync_timestamp=1586264004611&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fwarning%2Fdownload%2Fwarning-4.0.3.tgz",
|
||||
"integrity": "sha1-Fungd+uKhtavfWSqHgX9hbRnjKM=",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
|
13
package.json
13
package.json
@ -11,14 +11,13 @@
|
||||
"dependencies": {
|
||||
"@icon-park/vue-next": "^1.2.6",
|
||||
"animate.css": "^4.1.1",
|
||||
"ant-design-vue": "^2.1.2",
|
||||
"ant-design-vue": "^2.1.6",
|
||||
"chartist": "^0.11.4",
|
||||
"clipboard": "^2.0.6",
|
||||
"core-js": "^3.6.5",
|
||||
"crypto-js": "^4.0.0",
|
||||
"dexie": "^3.0.3",
|
||||
"file-saver": "^2.0.5",
|
||||
"html-to-image": "^1.6.0",
|
||||
"lodash": "^4.17.20",
|
||||
"mitt": "^2.1.0",
|
||||
"prosemirror-commands": "^1.1.7",
|
||||
@ -54,8 +53,8 @@
|
||||
"@types/prosemirror-schema-list": "^1.0.1",
|
||||
"@types/resize-observer-browser": "^0.1.4",
|
||||
"@types/tinycolor2": "^1.4.2",
|
||||
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
||||
"@typescript-eslint/parser": "^2.33.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
||||
"@typescript-eslint/parser": "^4.23.0",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||
@ -63,7 +62,7 @@
|
||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/compiler-sfc": "^3.0.11",
|
||||
"@vue/eslint-config-typescript": "^5.0.2",
|
||||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
"@vue/test-utils": "^2.0.0-0",
|
||||
"babel-plugin-import": "^1.13.3",
|
||||
"eslint": "^6.7.2",
|
||||
@ -71,12 +70,12 @@
|
||||
"husky": "^4.3.8",
|
||||
"less": "^3.12.2",
|
||||
"less-loader": "^7.1.0",
|
||||
"sass": "^1.26.5",
|
||||
"sass": "^1.32.13",
|
||||
"sass-loader": "^8.0.2",
|
||||
"stylelint": "^13.8.0",
|
||||
"stylelint-config-standard": "^20.0.0",
|
||||
"stylelint-webpack-plugin": "^2.1.1",
|
||||
"typescript": "~3.9.3",
|
||||
"typescript": "^4.2.4",
|
||||
"vue-jest": "^5.0.0-0"
|
||||
},
|
||||
"husky": {
|
||||
|
BIN
src/assets/fonts/字制区喜脉体.ttf
Normal file
BIN
src/assets/fonts/字制区喜脉体.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/assets/fonts/素材集市酷方体.ttf
Normal file
BIN
src/assets/fonts/素材集市酷方体.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/assets/fonts/阿里汉仪智能黑体.ttf
Normal file
BIN
src/assets/fonts/阿里汉仪智能黑体.ttf
Normal file
Binary file not shown.
@ -19,8 +19,8 @@
|
||||
src: url(../fonts/站酷快乐体.ttf);
|
||||
}
|
||||
@font-face {
|
||||
font-family: '站酷酷黑体';
|
||||
src: url(../fonts/站酷酷黑体.ttf);
|
||||
font-family: '字制区喜脉体';
|
||||
src: url(../fonts/字制区喜脉体.ttf);
|
||||
}
|
||||
@font-face {
|
||||
font-family: '素材集市康康体';
|
||||
@ -31,8 +31,8 @@
|
||||
src: url(../fonts/联盟起艺卢帅正锐黑体.ttf);
|
||||
}
|
||||
@font-face {
|
||||
font-family: '谦度手写楷体';
|
||||
src: url(../fonts/谦度手写楷体.ttf);
|
||||
font-family: '素材集市酷方体';
|
||||
src: url(../fonts/素材集市酷方体.ttf);
|
||||
}
|
||||
@font-face {
|
||||
font-family: '途牛类圆体';
|
||||
@ -43,6 +43,6 @@
|
||||
src: url(../fonts/锐字真言体.ttf);
|
||||
}
|
||||
@font-face {
|
||||
font-family: '问藏书房';
|
||||
src: url(../fonts/问藏书房.ttf);
|
||||
font-family: '阿里汉仪智能黑体';
|
||||
src: url(../fonts/阿里汉仪智能黑体.ttf);
|
||||
}
|
@ -37,7 +37,7 @@ export default defineComponent({
|
||||
|
||||
const color = computed(() => {
|
||||
const hsla = tinycolor(props.value).toHsl()
|
||||
if (hsla.s === 0) hsla.h = props.hue
|
||||
if (props.hue !== -1) hsla.h = props.hue
|
||||
return hsla
|
||||
})
|
||||
|
||||
@ -68,9 +68,9 @@ export default defineComponent({
|
||||
else if (left > containerWidth) h = 360
|
||||
else {
|
||||
percent = left * 100 / containerWidth
|
||||
h = (360 * percent / 100)
|
||||
h = 360 * percent / 100
|
||||
}
|
||||
if (color.value.h !== h) {
|
||||
if (props.hue === -1 || color.value.h !== h) {
|
||||
emit('colorChange', {
|
||||
h,
|
||||
l: color.value.l,
|
||||
|
@ -39,7 +39,7 @@ export default defineComponent({
|
||||
setup(props, { emit }) {
|
||||
const color = computed(() => {
|
||||
const hsva = tinycolor(props.value).toHsv()
|
||||
if (hsva.s === 0) hsva.h = props.hue
|
||||
if (props.hue !== -1) hsva.h = props.hue
|
||||
return hsva
|
||||
})
|
||||
|
||||
|
@ -140,7 +140,7 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const hue = ref(0)
|
||||
const hue = ref(-1)
|
||||
const recentColors = ref<string[]>([])
|
||||
|
||||
const color = computed({
|
||||
@ -162,6 +162,7 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
const selectPresetColor = (colorString: string) => {
|
||||
hue.value = tinycolor(colorString).toHsl().h
|
||||
emit('update:modelValue', colorString)
|
||||
}
|
||||
|
||||
@ -193,7 +194,10 @@ export default defineComponent({
|
||||
hue.value = value.h
|
||||
color.value = tinycolor(value).toRgb()
|
||||
}
|
||||
else color.value = value
|
||||
else {
|
||||
hue.value = tinycolor(value).toHsl().h
|
||||
color.value = value
|
||||
}
|
||||
|
||||
updateRecentColorsCache()
|
||||
}
|
||||
|
@ -1,256 +1,275 @@
|
||||
<template>
|
||||
<div class="writing-board" ref="writingBoardRef">
|
||||
<canvas class="canvas" ref="canvasRef"
|
||||
@mousedown="$event => handleMousedown($event)"
|
||||
@mousemove="$event => handleMousemove($event)"
|
||||
@mouseup="handleMouseup()"
|
||||
@mouseleave="handleMouseup(); mouseInCanvas = false"
|
||||
@mouseenter="mouseInCanvas = true"
|
||||
></canvas>
|
||||
|
||||
<div
|
||||
class="pen"
|
||||
:style="{
|
||||
left: mouse.x - penSize / 2 + 'px',
|
||||
top: mouse.y - 36 + penSize / 2 + 'px',
|
||||
color: color,
|
||||
}"
|
||||
v-if="mouseInCanvas && model === 'pen'"
|
||||
><IconWrite class="icon" size="36" /></div>
|
||||
|
||||
<div
|
||||
class="eraser"
|
||||
:style="{
|
||||
left: mouse.x - rubberSize / 2 + 'px',
|
||||
top: mouse.y - rubberSize / 2 + 'px',
|
||||
width: rubberSize + 'px',
|
||||
height: rubberSize + 'px',
|
||||
}"
|
||||
v-if="mouseInCanvas && model === 'eraser'"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, PropType, reactive, ref } from 'vue'
|
||||
|
||||
const penSize = 6
|
||||
const rubberSize = 80
|
||||
|
||||
export default defineComponent({
|
||||
name: 'writing-board',
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: '#ffcc00',
|
||||
},
|
||||
model: {
|
||||
type: String as PropType<'pen' | 'eraser'>,
|
||||
default: 'pen',
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
let ctx: CanvasRenderingContext2D | null = null
|
||||
const writingBoardRef = ref<HTMLElement>()
|
||||
const canvasRef = ref<HTMLCanvasElement>()
|
||||
|
||||
let lastPos = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
}
|
||||
let isMouseDown = false
|
||||
let lastTime = 0
|
||||
let lastLineWidth = -1
|
||||
|
||||
// 鼠标位置坐标:用于画笔或橡皮位置跟随
|
||||
const mouse = reactive({
|
||||
x: 0,
|
||||
y: 0,
|
||||
})
|
||||
// 更新鼠标位置坐标
|
||||
const updateMousePosition = (e: MouseEvent) => {
|
||||
mouse.x = e.pageX
|
||||
mouse.y = e.pageY
|
||||
}
|
||||
|
||||
// 鼠标是否处在画布范围内:处在范围内才会显示画笔或橡皮
|
||||
const mouseInCanvas = ref(false)
|
||||
|
||||
|
||||
// 初始化画布
|
||||
const initCanvas = () => {
|
||||
if (!canvasRef.value || !writingBoardRef.value) return
|
||||
|
||||
ctx = canvasRef.value.getContext('2d')
|
||||
if (!ctx) return
|
||||
|
||||
canvasRef.value.width = writingBoardRef.value.clientWidth
|
||||
canvasRef.value.height = writingBoardRef.value.clientHeight
|
||||
|
||||
canvasRef.value.style.width = writingBoardRef.value.clientWidth + 'px'
|
||||
canvasRef.value.style.height = writingBoardRef.value.clientHeight + 'px'
|
||||
|
||||
ctx.lineCap = 'round'
|
||||
ctx.lineJoin = 'round'
|
||||
}
|
||||
onMounted(initCanvas)
|
||||
|
||||
// 绘制画笔墨迹方法
|
||||
const draw = (posX: number, posY: number, lineWidth: number) => {
|
||||
if (!ctx) return
|
||||
|
||||
const lastPosX = lastPos.x
|
||||
const lastPosY = lastPos.y
|
||||
|
||||
ctx.lineWidth = lineWidth
|
||||
ctx.strokeStyle = props.color
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(lastPosX, lastPosY)
|
||||
ctx.lineTo(posX, posY)
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
}
|
||||
|
||||
// 擦除墨迹方法
|
||||
const erase = (posX: number, posY: number) => {
|
||||
if (!ctx || !canvasRef.value) return
|
||||
const lastPosX = lastPos.x
|
||||
const lastPosY = lastPos.y
|
||||
|
||||
const radius = rubberSize / 2
|
||||
|
||||
const sinRadius = radius * Math.sin(Math.atan((posY - lastPosY) / (posX - lastPosX)))
|
||||
const cosRadius = radius * Math.cos(Math.atan((posY - lastPosY) / (posX - lastPosX)))
|
||||
const rectPoint1: [number, number] = [lastPosX + sinRadius, lastPosY - cosRadius]
|
||||
const rectPoint2: [number, number] = [lastPosX - sinRadius, lastPosY + cosRadius]
|
||||
const rectPoint3: [number, number] = [posX + sinRadius, posY - cosRadius]
|
||||
const rectPoint4: [number, number] = [posX - sinRadius, posY + cosRadius]
|
||||
|
||||
ctx.save()
|
||||
ctx.beginPath()
|
||||
ctx.arc(posX, posY, radius, 0, Math.PI * 2)
|
||||
ctx.clip()
|
||||
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
|
||||
ctx.restore()
|
||||
|
||||
ctx.save()
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(...rectPoint1)
|
||||
ctx.lineTo(...rectPoint3)
|
||||
ctx.lineTo(...rectPoint4)
|
||||
ctx.lineTo(...rectPoint2)
|
||||
ctx.closePath()
|
||||
ctx.clip()
|
||||
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
// 准备开始绘制/擦除墨迹(落笔)
|
||||
const handleMousedown = (e: MouseEvent) => {
|
||||
isMouseDown = true
|
||||
lastPos = { x: e.offsetX, y: e.offsetY }
|
||||
lastTime = new Date().getTime()
|
||||
}
|
||||
|
||||
// 计算鼠标两次移动之间的距离
|
||||
const getDistance = (posX: number, posY: number) => {
|
||||
const lastPosX = lastPos.x
|
||||
const lastPosY = lastPos.y
|
||||
return Math.sqrt((posX - lastPosX) * (posX - lastPosX) + (posY - lastPosY) * (posY - lastPosY))
|
||||
}
|
||||
|
||||
// 根据鼠标两次移动之间的距离s和时间t计算绘制速度,速度越快,墨迹越细
|
||||
const getLineWidth = (s: number, t: number) => {
|
||||
const maxV = 10
|
||||
const minV = 0.1
|
||||
const maxWidth = penSize
|
||||
const minWidth = 3
|
||||
const v = s / t
|
||||
let lineWidth
|
||||
|
||||
if (v <= minV) lineWidth = maxWidth
|
||||
else if (v >= maxV) lineWidth = minWidth
|
||||
else lineWidth = maxWidth - v / maxV * maxWidth
|
||||
|
||||
if (lastLineWidth === -1) return lineWidth
|
||||
return lineWidth * 1 / 3 + lastLineWidth * 2 / 3
|
||||
}
|
||||
|
||||
// 开始绘制/擦除墨迹(移动)
|
||||
const handleMousemove = (e: MouseEvent) => {
|
||||
updateMousePosition(e)
|
||||
|
||||
if (!isMouseDown) return
|
||||
|
||||
const time = new Date().getTime()
|
||||
|
||||
if (props.model === 'pen') {
|
||||
const s = getDistance(e.offsetX, e.offsetY)
|
||||
const t = time - lastTime
|
||||
const lineWidth = getLineWidth(s, t)
|
||||
|
||||
draw(e.offsetX, e.offsetY, lineWidth)
|
||||
lastLineWidth = lineWidth
|
||||
}
|
||||
else erase(e.offsetX, e.offsetY)
|
||||
|
||||
lastPos = { x: e.offsetX, y: e.offsetY }
|
||||
lastTime = new Date().getTime()
|
||||
}
|
||||
|
||||
// 结束绘制/擦除墨迹(停笔)
|
||||
const handleMouseup = () => {
|
||||
if (!isMouseDown) return
|
||||
isMouseDown = false
|
||||
}
|
||||
|
||||
// 清空画布
|
||||
const clearCanvas = () => {
|
||||
if (!ctx || !canvasRef.value) return
|
||||
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
|
||||
}
|
||||
|
||||
return {
|
||||
mouse,
|
||||
mouseInCanvas,
|
||||
penSize,
|
||||
rubberSize,
|
||||
writingBoardRef,
|
||||
canvasRef,
|
||||
handleMousedown,
|
||||
handleMousemove,
|
||||
handleMouseup,
|
||||
clearCanvas,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.writing-board {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 8;
|
||||
cursor: none;
|
||||
}
|
||||
.eraser, .pen {
|
||||
pointer-events: none;
|
||||
position: fixed;
|
||||
z-index: 9;
|
||||
|
||||
.icon {
|
||||
filter: drop-shadow(2px 2px 2px #555);
|
||||
}
|
||||
}
|
||||
.eraser {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
border: 4px solid rgba($color: #555, $alpha: .15);
|
||||
color: rgba($color: #555, $alpha: .75);
|
||||
}
|
||||
<template>
|
||||
<div class="writing-board" ref="writingBoardRef">
|
||||
<canvas class="canvas" ref="canvasRef"
|
||||
@mousedown="$event => handleMousedown($event)"
|
||||
@mousemove="$event => handleMousemove($event)"
|
||||
@mouseup="handleMouseup()"
|
||||
@touchstart="$event => handleMousedown($event)"
|
||||
@touchmove="$event => handleMousemove($event)"
|
||||
@touchend="handleMouseup(); mouseInCanvas = false"
|
||||
@mouseleave="handleMouseup(); mouseInCanvas = false"
|
||||
@mouseenter="mouseInCanvas = true"
|
||||
></canvas>
|
||||
|
||||
<div
|
||||
class="pen"
|
||||
:style="{
|
||||
left: mouse.x - penSize / 2 + 'px',
|
||||
top: mouse.y - 36 + penSize / 2 + 'px',
|
||||
color: color,
|
||||
}"
|
||||
v-if="mouseInCanvas && model === 'pen'"
|
||||
><IconWrite class="icon" size="36" /></div>
|
||||
|
||||
<div
|
||||
class="eraser"
|
||||
:style="{
|
||||
left: mouse.x - rubberSize / 2 + 'px',
|
||||
top: mouse.y - rubberSize / 2 + 'px',
|
||||
width: rubberSize + 'px',
|
||||
height: rubberSize + 'px',
|
||||
}"
|
||||
v-if="mouseInCanvas && model === 'eraser'"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, PropType, reactive, ref } from 'vue'
|
||||
|
||||
const penSize = 6
|
||||
const rubberSize = 80
|
||||
|
||||
export default defineComponent({
|
||||
name: 'writing-board',
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: '#ffcc00',
|
||||
},
|
||||
model: {
|
||||
type: String as PropType<'pen' | 'eraser'>,
|
||||
default: 'pen',
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
let ctx: CanvasRenderingContext2D | null = null
|
||||
const writingBoardRef = ref<HTMLElement>()
|
||||
const canvasRef = ref<HTMLCanvasElement>()
|
||||
|
||||
let lastPos = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
}
|
||||
let isMouseDown = false
|
||||
let lastTime = 0
|
||||
let lastLineWidth = -1
|
||||
|
||||
// 鼠标位置坐标:用于画笔或橡皮位置跟随
|
||||
const mouse = reactive({
|
||||
x: 0,
|
||||
y: 0,
|
||||
})
|
||||
|
||||
// 更新鼠标位置坐标
|
||||
const updateMousePosition = (x: number, y: number) => {
|
||||
mouse.x = x
|
||||
mouse.y = y
|
||||
}
|
||||
|
||||
// 鼠标是否处在画布范围内:处在范围内才会显示画笔或橡皮
|
||||
const mouseInCanvas = ref(false)
|
||||
|
||||
|
||||
// 初始化画布
|
||||
const initCanvas = () => {
|
||||
if (!canvasRef.value || !writingBoardRef.value) return
|
||||
|
||||
ctx = canvasRef.value.getContext('2d')
|
||||
if (!ctx) return
|
||||
|
||||
canvasRef.value.width = writingBoardRef.value.clientWidth
|
||||
canvasRef.value.height = writingBoardRef.value.clientHeight
|
||||
|
||||
canvasRef.value.style.width = writingBoardRef.value.clientWidth + 'px'
|
||||
canvasRef.value.style.height = writingBoardRef.value.clientHeight + 'px'
|
||||
|
||||
ctx.lineCap = 'round'
|
||||
ctx.lineJoin = 'round'
|
||||
}
|
||||
onMounted(initCanvas)
|
||||
|
||||
// 绘制画笔墨迹方法
|
||||
const draw = (posX: number, posY: number, lineWidth: number) => {
|
||||
if (!ctx) return
|
||||
|
||||
const lastPosX = lastPos.x
|
||||
const lastPosY = lastPos.y
|
||||
|
||||
ctx.lineWidth = lineWidth
|
||||
ctx.strokeStyle = props.color
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(lastPosX, lastPosY)
|
||||
ctx.lineTo(posX, posY)
|
||||
ctx.stroke()
|
||||
ctx.closePath()
|
||||
}
|
||||
|
||||
// 擦除墨迹方法
|
||||
const erase = (posX: number, posY: number) => {
|
||||
if (!ctx || !canvasRef.value) return
|
||||
const lastPosX = lastPos.x
|
||||
const lastPosY = lastPos.y
|
||||
|
||||
const radius = rubberSize / 2
|
||||
|
||||
const sinRadius = radius * Math.sin(Math.atan((posY - lastPosY) / (posX - lastPosX)))
|
||||
const cosRadius = radius * Math.cos(Math.atan((posY - lastPosY) / (posX - lastPosX)))
|
||||
const rectPoint1: [number, number] = [lastPosX + sinRadius, lastPosY - cosRadius]
|
||||
const rectPoint2: [number, number] = [lastPosX - sinRadius, lastPosY + cosRadius]
|
||||
const rectPoint3: [number, number] = [posX + sinRadius, posY - cosRadius]
|
||||
const rectPoint4: [number, number] = [posX - sinRadius, posY + cosRadius]
|
||||
|
||||
ctx.save()
|
||||
ctx.beginPath()
|
||||
ctx.arc(posX, posY, radius, 0, Math.PI * 2)
|
||||
ctx.clip()
|
||||
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
|
||||
ctx.restore()
|
||||
|
||||
ctx.save()
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(...rectPoint1)
|
||||
ctx.lineTo(...rectPoint3)
|
||||
ctx.lineTo(...rectPoint4)
|
||||
ctx.lineTo(...rectPoint2)
|
||||
ctx.closePath()
|
||||
ctx.clip()
|
||||
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
// 计算鼠标两次移动之间的距离
|
||||
const getDistance = (posX: number, posY: number) => {
|
||||
const lastPosX = lastPos.x
|
||||
const lastPosY = lastPos.y
|
||||
return Math.sqrt((posX - lastPosX) * (posX - lastPosX) + (posY - lastPosY) * (posY - lastPosY))
|
||||
}
|
||||
|
||||
// 根据鼠标两次移动之间的距离s和时间t计算绘制速度,速度越快,墨迹越细
|
||||
const getLineWidth = (s: number, t: number) => {
|
||||
const maxV = 10
|
||||
const minV = 0.1
|
||||
const maxWidth = penSize
|
||||
const minWidth = 3
|
||||
const v = s / t
|
||||
let lineWidth
|
||||
|
||||
if (v <= minV) lineWidth = maxWidth
|
||||
else if (v >= maxV) lineWidth = minWidth
|
||||
else lineWidth = maxWidth - v / maxV * maxWidth
|
||||
|
||||
if (lastLineWidth === -1) return lineWidth
|
||||
return lineWidth * 1 / 3 + lastLineWidth * 2 / 3
|
||||
}
|
||||
|
||||
// 路径操作
|
||||
const handleMove = (x: number, y: number) => {
|
||||
const time = new Date().getTime()
|
||||
|
||||
if (props.model === 'pen') {
|
||||
const s = getDistance(x, y)
|
||||
const t = time - lastTime
|
||||
const lineWidth = getLineWidth(s, t)
|
||||
|
||||
draw(x, y, lineWidth)
|
||||
lastLineWidth = lineWidth
|
||||
}
|
||||
else erase(x, y)
|
||||
|
||||
lastPos = {x, y}
|
||||
lastTime = new Date().getTime()
|
||||
}
|
||||
|
||||
// 处理鼠标(触摸)事件
|
||||
// 准备开始绘制/擦除墨迹(落笔)
|
||||
const handleMousedown = (e: MouseEvent | TouchEvent) => {
|
||||
const x = e instanceof MouseEvent ? e.offsetX : e.changedTouches[0].pageX
|
||||
const y = e instanceof MouseEvent ? e.offsetY : e.changedTouches[0].pageY
|
||||
|
||||
isMouseDown = true
|
||||
lastPos = { x, y }
|
||||
lastTime = new Date().getTime()
|
||||
|
||||
if (e instanceof TouchEvent) {
|
||||
updateMousePosition(x, y)
|
||||
mouseInCanvas.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 开始绘制/擦除墨迹(移动)
|
||||
const handleMousemove = (e: MouseEvent | TouchEvent) => {
|
||||
const x = e instanceof MouseEvent ? e.offsetX : e.changedTouches[0].pageX
|
||||
const y = e instanceof MouseEvent ? e.offsetY : e.changedTouches[0].pageY
|
||||
|
||||
updateMousePosition(x, y)
|
||||
|
||||
if (isMouseDown) handleMove(x, y)
|
||||
}
|
||||
|
||||
// 结束绘制/擦除墨迹(停笔)
|
||||
const handleMouseup = () => {
|
||||
if (!isMouseDown) return
|
||||
isMouseDown = false
|
||||
}
|
||||
|
||||
// 清空画布
|
||||
const clearCanvas = () => {
|
||||
if (!ctx || !canvasRef.value) return
|
||||
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
|
||||
}
|
||||
|
||||
return {
|
||||
mouse,
|
||||
mouseInCanvas,
|
||||
penSize,
|
||||
rubberSize,
|
||||
writingBoardRef,
|
||||
canvasRef,
|
||||
handleMousedown,
|
||||
handleMousemove,
|
||||
handleMouseup,
|
||||
clearCanvas,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.writing-board {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 8;
|
||||
cursor: none;
|
||||
}
|
||||
.eraser, .pen {
|
||||
pointer-events: none;
|
||||
position: fixed;
|
||||
z-index: 9;
|
||||
|
||||
.icon {
|
||||
filter: drop-shadow(2px 2px 2px #555);
|
||||
}
|
||||
}
|
||||
.eraser {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
border: 4px solid rgba($color: #555, $alpha: .15);
|
||||
color: rgba($color: #555, $alpha: .75);
|
||||
}
|
||||
</style>
|
@ -35,11 +35,11 @@ export const WEB_FONTS = [
|
||||
{ label: '峰广明锐体', value: '峰广明锐体' },
|
||||
{ label: '摄图摩登小方体', value: '摄图摩登小方体' },
|
||||
{ label: '站酷快乐体', value: '站酷快乐体' },
|
||||
{ label: '站酷酷黑体', value: '站酷酷黑体' },
|
||||
{ label: '字制区喜脉体', value: '字制区喜脉体' },
|
||||
{ label: '素材集市康康体', value: '素材集市康康体' },
|
||||
{ label: '联盟起艺卢帅正锐黑体', value: '联盟起艺卢帅正锐黑体' },
|
||||
{ label: '谦度手写楷体', value: '谦度手写楷体' },
|
||||
{ label: '素材集市酷方体', value: '素材集市酷方体' },
|
||||
{ label: '途牛类圆体', value: '途牛类圆体' },
|
||||
{ label: '锐字真言体', value: '锐字真言体' },
|
||||
{ label: '问藏书房', value: '问藏书房' },
|
||||
{ label: '阿里汉仪智能黑体', value: '阿里汉仪智能黑体' },
|
||||
]
|
@ -55,8 +55,8 @@ export const HOTKEY_DOC = [
|
||||
{ label: '放大画布', value: 'Ctrl + =' },
|
||||
{ label: '缩小画布', value: 'Ctrl + -' },
|
||||
{ label: '缩放画布到合适大小', value: 'Ctrl + 0' },
|
||||
{ label: '编辑上一页', value: '↑ / ←' },
|
||||
{ label: '编辑下一页', value: '↓ / →' },
|
||||
{ label: '编辑上一页', value: '↑ / ← / 鼠标上滚' },
|
||||
{ label: '编辑下一页', value: '↓ / → / 鼠标下滚' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -70,6 +70,7 @@ export const HOTKEY_DOC = [
|
||||
{ label: '置底层', value: 'Alt + B' },
|
||||
{ label: '锁定宽高比例', value: '按住 Ctrl 或 Shift' },
|
||||
{ label: '创建水平 / 垂直线条', value: '按住 Ctrl 或 Shift' },
|
||||
{ label: '切换焦点元素', value: 'Tab' },
|
||||
{ label: '确认图片裁剪', value: 'Enter' },
|
||||
],
|
||||
},
|
||||
|
@ -63,6 +63,7 @@ export default () => {
|
||||
left: (VIEWPORT_SIZE - width) / 2,
|
||||
top: (VIEWPORT_SIZE * viewportRatio.value - height) / 2,
|
||||
fixedRatio: true,
|
||||
rotate: 0,
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -147,6 +148,7 @@ export default () => {
|
||||
width,
|
||||
height,
|
||||
content,
|
||||
rotate: 0,
|
||||
})
|
||||
}
|
||||
|
||||
@ -168,6 +170,7 @@ export default () => {
|
||||
path: data.path,
|
||||
fill: themeColor.value,
|
||||
fixedRatio: false,
|
||||
rotate: 0,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,29 @@
|
||||
import { computed } from 'vue'
|
||||
import { MutationTypes, useStore } from '@/store'
|
||||
import { Slide } from '@/types/slides'
|
||||
import { PPTElement, Slide } from '@/types/slides'
|
||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||
|
||||
export default () => {
|
||||
const store = useStore()
|
||||
const activeElementIdList = computed(() => store.state.activeElementIdList)
|
||||
const activeGroupElementId = computed(() => store.state.activeGroupElementId)
|
||||
const currentSlide = computed<Slide>(() => store.getters.currentSlide)
|
||||
|
||||
const { addHistorySnapshot } = useHistorySnapshot()
|
||||
|
||||
// 删除全部选中元素
|
||||
// 组合元素成员中,存在被选中可独立操作的元素时,优先删除该元素。否则默认删除所有被选中的元素
|
||||
const deleteElement = () => {
|
||||
if (!activeElementIdList.value.length) return
|
||||
const newElementList = currentSlide.value.elements.filter(el => !activeElementIdList.value.includes(el.id))
|
||||
|
||||
let newElementList: PPTElement[] = []
|
||||
if (activeGroupElementId.value) {
|
||||
newElementList = currentSlide.value.elements.filter(el => el.id !== activeGroupElementId.value)
|
||||
}
|
||||
else {
|
||||
newElementList = currentSlide.value.elements.filter(el => !activeElementIdList.value.includes(el.id))
|
||||
}
|
||||
|
||||
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [])
|
||||
store.commit(MutationTypes.UPDATE_SLIDE, { elements: newElementList })
|
||||
addHistorySnapshot()
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { computed, onMounted, onUnmounted } from 'vue'
|
||||
import { MutationTypes, useStore } from '@/store'
|
||||
import { ElementOrderCommand, ElementOrderCommands } from '@/types/edit'
|
||||
import { PPTElement } from '@/types/slides'
|
||||
import { PPTElement, Slide } from '@/types/slides'
|
||||
import { KEYS } from '@/configs/hotkey'
|
||||
|
||||
import useSlideHandler from './useSlideHandler'
|
||||
@ -24,6 +24,7 @@ export default () => {
|
||||
const disableHotkeys = computed(() => store.state.disableHotkeys)
|
||||
const activeElementIdList = computed(() => store.state.activeElementIdList)
|
||||
const handleElement = computed<PPTElement>(() => store.getters.handleElement)
|
||||
const currentSlide = computed<Slide>(() => store.getters.currentSlide)
|
||||
|
||||
const editorAreaFocus = computed(() => store.state.editorAreaFocus)
|
||||
const thumbnailsFocus = computed(() => store.state.thumbnailsFocus)
|
||||
@ -103,15 +104,31 @@ export default () => {
|
||||
createSlide()
|
||||
}
|
||||
|
||||
const tabActiveElement = () => {
|
||||
if (!currentSlide.value.elements.length) return
|
||||
if (!handleElement.value) {
|
||||
const firstElement = currentSlide.value.elements[0]
|
||||
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [firstElement.id])
|
||||
return
|
||||
}
|
||||
|
||||
const currentIndex = currentSlide.value.elements.findIndex(el => el.id === handleElement.value.id)
|
||||
const nextIndex = currentIndex >= currentSlide.value.elements.length - 1 ? 0 : currentIndex + 1
|
||||
const nextElementId = currentSlide.value.elements[nextIndex].id
|
||||
|
||||
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [nextElementId])
|
||||
}
|
||||
|
||||
const keydownListener = (e: KeyboardEvent) => {
|
||||
const { ctrlKey, shiftKey, altKey, metaKey } = e
|
||||
|
||||
const ctrlOrMetaKeyActive = ctrlKey || metaKey
|
||||
|
||||
const key = e.key.toUpperCase()
|
||||
|
||||
if (ctrlKey && !ctrlKeyActive.value) store.commit(MutationTypes.SET_CTRL_KEY_STATE, true)
|
||||
if (ctrlOrMetaKeyActive && !ctrlKeyActive.value) store.commit(MutationTypes.SET_CTRL_KEY_STATE, true)
|
||||
if (shiftKey && !shiftKeyActive.value) store.commit(MutationTypes.SET_SHIFT_KEY_STATE, true)
|
||||
|
||||
if (ctrlKey && key === KEYS.F) {
|
||||
if (ctrlOrMetaKeyActive && key === KEYS.F) {
|
||||
e.preventDefault()
|
||||
enterScreening()
|
||||
store.commit(MutationTypes.SET_CTRL_KEY_STATE, false)
|
||||
@ -119,47 +136,47 @@ export default () => {
|
||||
|
||||
if (!editorAreaFocus.value && !thumbnailsFocus.value) return
|
||||
|
||||
if ((ctrlKey || metaKey) && key === KEYS.C) {
|
||||
if (ctrlOrMetaKeyActive && key === KEYS.C) {
|
||||
if (disableHotkeys.value) return
|
||||
e.preventDefault()
|
||||
copy()
|
||||
}
|
||||
if (ctrlKey && key === KEYS.X) {
|
||||
if (ctrlOrMetaKeyActive && key === KEYS.X) {
|
||||
if (disableHotkeys.value) return
|
||||
e.preventDefault()
|
||||
cut()
|
||||
}
|
||||
if (ctrlKey && key === KEYS.D) {
|
||||
if (ctrlOrMetaKeyActive && key === KEYS.D) {
|
||||
if (disableHotkeys.value) return
|
||||
e.preventDefault()
|
||||
quickCopy()
|
||||
}
|
||||
if (ctrlKey && key === KEYS.Z) {
|
||||
if (ctrlOrMetaKeyActive && key === KEYS.Z) {
|
||||
if (disableHotkeys.value) return
|
||||
e.preventDefault()
|
||||
undo()
|
||||
}
|
||||
if (ctrlKey && key === KEYS.Y) {
|
||||
if (ctrlOrMetaKeyActive && key === KEYS.Y) {
|
||||
if (disableHotkeys.value) return
|
||||
e.preventDefault()
|
||||
redo()
|
||||
}
|
||||
if (ctrlKey && key === KEYS.A) {
|
||||
if (ctrlOrMetaKeyActive && key === KEYS.A) {
|
||||
if (disableHotkeys.value) return
|
||||
e.preventDefault()
|
||||
selectAll()
|
||||
}
|
||||
if (ctrlKey && key === KEYS.L) {
|
||||
if (ctrlOrMetaKeyActive && key === KEYS.L) {
|
||||
if (disableHotkeys.value) return
|
||||
e.preventDefault()
|
||||
lock()
|
||||
}
|
||||
if (!shiftKey && ctrlKey && key === KEYS.G) {
|
||||
if (!shiftKey && ctrlOrMetaKeyActive && key === KEYS.G) {
|
||||
if (disableHotkeys.value) return
|
||||
e.preventDefault()
|
||||
combine()
|
||||
}
|
||||
if (shiftKey && ctrlKey && key === KEYS.G) {
|
||||
if (shiftKey && ctrlOrMetaKeyActive && key === KEYS.G) {
|
||||
if (disableHotkeys.value) return
|
||||
e.preventDefault()
|
||||
uncombine()
|
||||
@ -219,6 +236,11 @@ export default () => {
|
||||
e.preventDefault()
|
||||
setCanvasPercentage(90)
|
||||
}
|
||||
if (key === KEYS.TAB) {
|
||||
if (disableHotkeys.value) return
|
||||
e.preventDefault()
|
||||
tabActiveElement()
|
||||
}
|
||||
}
|
||||
|
||||
const keyupListener = () => {
|
||||
|
@ -1,134 +1,133 @@
|
||||
import { computed } from 'vue'
|
||||
import { MutationTypes, useStore } from '@/store'
|
||||
import { Slide } from '@/types/slides'
|
||||
import { createRandomCode } from '@/utils/common'
|
||||
import { copyText, readClipboard } from '@/utils/clipboard'
|
||||
import { encrypt } from '@/utils/crypto'
|
||||
import { KEYS } from '@/configs/hotkey'
|
||||
import { message } from 'ant-design-vue'
|
||||
import usePasteTextClipboardData from '@/hooks/usePasteTextClipboardData'
|
||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||
|
||||
export default () => {
|
||||
const store = useStore()
|
||||
const slideIndex = computed(() => store.state.slideIndex)
|
||||
const theme = computed(() => store.state.theme)
|
||||
const slides = computed(() => store.state.slides)
|
||||
const currentSlide = computed<Slide>(() => store.getters.currentSlide)
|
||||
|
||||
const selectedSlidesIndex = computed(() => [...store.state.selectedSlidesIndex, slideIndex.value])
|
||||
const selectedSlides = computed(() => slides.value.filter((item, index) => selectedSlidesIndex.value.includes(index)))
|
||||
const selectedSlidesId = computed(() => selectedSlides.value.map(item => item.id))
|
||||
|
||||
const { pasteTextClipboardData } = usePasteTextClipboardData()
|
||||
const { addHistorySnapshot } = useHistorySnapshot()
|
||||
|
||||
// 重置幻灯片
|
||||
const resetSlides = () => {
|
||||
const emptySlide = {
|
||||
id: createRandomCode(8),
|
||||
elements: [],
|
||||
background: {
|
||||
type: 'solid',
|
||||
color: theme.value.backgroundColor,
|
||||
},
|
||||
}
|
||||
store.commit(MutationTypes.UPDATE_SLIDE_INDEX, 0)
|
||||
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [])
|
||||
store.commit(MutationTypes.SET_SLIDES, [emptySlide])
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动页面焦点
|
||||
* @param command 移动页面焦点命令:上移、下移
|
||||
*/
|
||||
const updateSlideIndex = (command: string) => {
|
||||
let targetIndex = 0
|
||||
if (command === KEYS.UP && slideIndex.value > 0) {
|
||||
targetIndex = slideIndex.value - 1
|
||||
}
|
||||
else if (command === KEYS.DOWN && slideIndex.value < slides.value.length - 1) {
|
||||
targetIndex = slideIndex.value + 1
|
||||
}
|
||||
store.commit(MutationTypes.UPDATE_SLIDE_INDEX, targetIndex)
|
||||
}
|
||||
|
||||
// 将当前页面数据加密后复制到剪贴板
|
||||
const copySlide = () => {
|
||||
const text = encrypt(JSON.stringify({
|
||||
type: 'slides',
|
||||
data: selectedSlides.value,
|
||||
}))
|
||||
|
||||
copyText(text).then(() => {
|
||||
store.commit(MutationTypes.SET_THUMBNAILS_FOCUS, true)
|
||||
})
|
||||
}
|
||||
|
||||
// 尝试将剪贴板页面数据解密后添加到下一页(粘贴)
|
||||
const pasteSlide = () => {
|
||||
readClipboard().then(text => {
|
||||
pasteTextClipboardData(text, { onlySlide: true })
|
||||
}).catch(err => message.warning(err))
|
||||
}
|
||||
|
||||
// 创建一页空白页并添加到下一页
|
||||
const createSlide = () => {
|
||||
const emptySlide = {
|
||||
id: createRandomCode(8),
|
||||
elements: [],
|
||||
background: {
|
||||
type: 'solid',
|
||||
color: theme.value.backgroundColor,
|
||||
},
|
||||
}
|
||||
store.commit(MutationTypes.ADD_SLIDE, emptySlide)
|
||||
addHistorySnapshot()
|
||||
}
|
||||
|
||||
// 将当前页复制一份到下一页
|
||||
const copyAndPasteSlide = () => {
|
||||
store.commit(MutationTypes.ADD_SLIDE, {
|
||||
...currentSlide.value,
|
||||
id: createRandomCode(8),
|
||||
})
|
||||
addHistorySnapshot()
|
||||
}
|
||||
|
||||
// 删除当前页,若将删除全部页面,则执行重置幻灯片操作
|
||||
const deleteSlide = (targetSlidesId = selectedSlidesId.value) => {
|
||||
if (slides.value.length === targetSlidesId.length) resetSlides()
|
||||
else store.commit(MutationTypes.DELETE_SLIDE, targetSlidesId)
|
||||
|
||||
store.commit(MutationTypes.UPDATE_SELECTED_SLIDES_INDEX, [])
|
||||
|
||||
addHistorySnapshot()
|
||||
}
|
||||
|
||||
// 将当前页复制后删除(剪切)
|
||||
// 由于复制操作会导致多选状态消失,所以需要提前将需要删除的页面ID进行缓存
|
||||
const cutSlide = () => {
|
||||
const targetSlidesId = [...selectedSlidesId.value]
|
||||
copySlide()
|
||||
deleteSlide(targetSlidesId)
|
||||
}
|
||||
|
||||
// 选中全部幻灯片
|
||||
const selectAllSlide = () => {
|
||||
const newSelectedSlidesIndex = Array.from(Array(slides.value.length), (item, index) => index)
|
||||
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [])
|
||||
store.commit(MutationTypes.UPDATE_SELECTED_SLIDES_INDEX, newSelectedSlidesIndex)
|
||||
}
|
||||
|
||||
return {
|
||||
resetSlides,
|
||||
updateSlideIndex,
|
||||
copySlide,
|
||||
pasteSlide,
|
||||
createSlide,
|
||||
copyAndPasteSlide,
|
||||
deleteSlide,
|
||||
cutSlide,
|
||||
selectAllSlide,
|
||||
}
|
||||
import { computed } from 'vue'
|
||||
import { MutationTypes, useStore } from '@/store'
|
||||
import { Slide } from '@/types/slides'
|
||||
import { createRandomCode } from '@/utils/common'
|
||||
import { copyText, readClipboard } from '@/utils/clipboard'
|
||||
import { encrypt } from '@/utils/crypto'
|
||||
import { KEYS } from '@/configs/hotkey'
|
||||
import { message } from 'ant-design-vue'
|
||||
import usePasteTextClipboardData from '@/hooks/usePasteTextClipboardData'
|
||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||
|
||||
export default () => {
|
||||
const store = useStore()
|
||||
const slideIndex = computed(() => store.state.slideIndex)
|
||||
const theme = computed(() => store.state.theme)
|
||||
const slides = computed(() => store.state.slides)
|
||||
const currentSlide = computed<Slide>(() => store.getters.currentSlide)
|
||||
|
||||
const selectedSlidesIndex = computed(() => [...store.state.selectedSlidesIndex, slideIndex.value])
|
||||
const selectedSlides = computed(() => slides.value.filter((item, index) => selectedSlidesIndex.value.includes(index)))
|
||||
const selectedSlidesId = computed(() => selectedSlides.value.map(item => item.id))
|
||||
|
||||
const { pasteTextClipboardData } = usePasteTextClipboardData()
|
||||
const { addHistorySnapshot } = useHistorySnapshot()
|
||||
|
||||
// 重置幻灯片
|
||||
const resetSlides = () => {
|
||||
const emptySlide = {
|
||||
id: createRandomCode(8),
|
||||
elements: [],
|
||||
background: {
|
||||
type: 'solid',
|
||||
color: theme.value.backgroundColor,
|
||||
},
|
||||
}
|
||||
store.commit(MutationTypes.UPDATE_SLIDE_INDEX, 0)
|
||||
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [])
|
||||
store.commit(MutationTypes.SET_SLIDES, [emptySlide])
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动页面焦点
|
||||
* @param command 移动页面焦点命令:上移、下移
|
||||
*/
|
||||
const updateSlideIndex = (command: string) => {
|
||||
if (command === KEYS.UP && slideIndex.value > 0) {
|
||||
store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value - 1)
|
||||
}
|
||||
else if (command === KEYS.DOWN && slideIndex.value < slides.value.length - 1) {
|
||||
store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// 将当前页面数据加密后复制到剪贴板
|
||||
const copySlide = () => {
|
||||
const text = encrypt(JSON.stringify({
|
||||
type: 'slides',
|
||||
data: selectedSlides.value,
|
||||
}))
|
||||
|
||||
copyText(text).then(() => {
|
||||
store.commit(MutationTypes.SET_THUMBNAILS_FOCUS, true)
|
||||
})
|
||||
}
|
||||
|
||||
// 尝试将剪贴板页面数据解密后添加到下一页(粘贴)
|
||||
const pasteSlide = () => {
|
||||
readClipboard().then(text => {
|
||||
pasteTextClipboardData(text, { onlySlide: true })
|
||||
}).catch(err => message.warning(err))
|
||||
}
|
||||
|
||||
// 创建一页空白页并添加到下一页
|
||||
const createSlide = () => {
|
||||
const emptySlide = {
|
||||
id: createRandomCode(8),
|
||||
elements: [],
|
||||
background: {
|
||||
type: 'solid',
|
||||
color: theme.value.backgroundColor,
|
||||
},
|
||||
}
|
||||
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [])
|
||||
store.commit(MutationTypes.ADD_SLIDE, emptySlide)
|
||||
addHistorySnapshot()
|
||||
}
|
||||
|
||||
// 将当前页复制一份到下一页
|
||||
const copyAndPasteSlide = () => {
|
||||
store.commit(MutationTypes.ADD_SLIDE, {
|
||||
...currentSlide.value,
|
||||
id: createRandomCode(8),
|
||||
})
|
||||
addHistorySnapshot()
|
||||
}
|
||||
|
||||
// 删除当前页,若将删除全部页面,则执行重置幻灯片操作
|
||||
const deleteSlide = (targetSlidesId = selectedSlidesId.value) => {
|
||||
if (slides.value.length === targetSlidesId.length) resetSlides()
|
||||
else store.commit(MutationTypes.DELETE_SLIDE, targetSlidesId)
|
||||
|
||||
store.commit(MutationTypes.UPDATE_SELECTED_SLIDES_INDEX, [])
|
||||
|
||||
addHistorySnapshot()
|
||||
}
|
||||
|
||||
// 将当前页复制后删除(剪切)
|
||||
// 由于复制操作会导致多选状态消失,所以需要提前将需要删除的页面ID进行缓存
|
||||
const cutSlide = () => {
|
||||
const targetSlidesId = [...selectedSlidesId.value]
|
||||
copySlide()
|
||||
deleteSlide(targetSlidesId)
|
||||
}
|
||||
|
||||
// 选中全部幻灯片
|
||||
const selectAllSlide = () => {
|
||||
const newSelectedSlidesIndex = Array.from(Array(slides.value.length), (item, index) => index)
|
||||
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [])
|
||||
store.commit(MutationTypes.UPDATE_SELECTED_SLIDES_INDEX, newSelectedSlidesIndex)
|
||||
}
|
||||
|
||||
return {
|
||||
resetSlides,
|
||||
updateSlideIndex,
|
||||
copySlide,
|
||||
pasteSlide,
|
||||
createSlide,
|
||||
copyAndPasteSlide,
|
||||
deleteSlide,
|
||||
cutSlide,
|
||||
selectAllSlide,
|
||||
}
|
||||
}
|
@ -23,19 +23,24 @@ export default () => {
|
||||
const copyOfActiveElementList: PPTElement[] = JSON.parse(JSON.stringify(activeElementList.value))
|
||||
const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))
|
||||
|
||||
// 将选中的元素按位置(从左到右)排序
|
||||
copyOfActiveElementList.sort((elementA, elementB) => {
|
||||
const { minX: elAMinX } = getElementRange(elementA)
|
||||
const { minX: elBMinX } = getElementRange(elementB)
|
||||
return elAMinX - elBMinX
|
||||
})
|
||||
|
||||
let totaiWidth = 0
|
||||
// 计算元素均匀分布所需要的间隔:
|
||||
// (所选元素整体范围 - 所有所选元素宽度和) / (所选元素数 - 1)
|
||||
let totalWidth = 0
|
||||
for (const element of activeElementList.value) {
|
||||
const { minX: elMinX, maxX: elMaxX } = getElementRange(element)
|
||||
totaiWidth += (elMaxX - elMinX)
|
||||
totalWidth += (elMaxX - elMinX)
|
||||
}
|
||||
const span = ((maxX - minX) - totaiWidth) / (activeElementList.value.length - 1)
|
||||
const span = ((maxX - minX) - totalWidth) / (activeElementList.value.length - 1)
|
||||
|
||||
// 将所选元素按位置顺序依次计算目标位置
|
||||
// 注意pos并非元素目标left值,而是目标位置范围最小值(元素旋转后的left值 ≠ 范围最小值)
|
||||
const sortedElementData: SortedElementData[] = []
|
||||
for (const element of copyOfActiveElementList) {
|
||||
if (!sortedElementData.length) {
|
||||
@ -52,6 +57,8 @@ export default () => {
|
||||
sortedElementData.push({ el: element, pos: lastItemPos + lastElementWidth + span })
|
||||
}
|
||||
|
||||
// 根据目标位置计算元素最终目标left值
|
||||
// 对于旋转后的元素,需要计算旋转前后left的偏移来做校正
|
||||
for (const element of newElementList) {
|
||||
if (!activeElementIdList.value.includes(element.id)) continue
|
||||
|
||||
@ -76,7 +83,7 @@ export default () => {
|
||||
addHistorySnapshot()
|
||||
}
|
||||
|
||||
// 垂直均匀排列
|
||||
// 垂直均匀排列(逻辑类似水平均匀排列方法)
|
||||
const uniformVerticalDisplay = () => {
|
||||
const { minY, maxY } = getElementListRange(activeElementList.value)
|
||||
const copyOfActiveElementList: PPTElement[] = JSON.parse(JSON.stringify(activeElementList.value))
|
||||
@ -88,12 +95,12 @@ export default () => {
|
||||
return elAMinY - elBMinY
|
||||
})
|
||||
|
||||
let totaiHeight = 0
|
||||
let totalHeight = 0
|
||||
for (const element of activeElementList.value) {
|
||||
const { minY: elMinY, maxY: elMaxY } = getElementRange(element)
|
||||
totaiHeight += (elMaxY - elMinY)
|
||||
totalHeight += (elMaxY - elMinY)
|
||||
}
|
||||
const span = ((maxY - minY) - totaiHeight) / (activeElementList.value.length - 1)
|
||||
const span = ((maxY - minY) - totalHeight) / (activeElementList.value.length - 1)
|
||||
|
||||
const sortedElementData: SortedElementData[] = []
|
||||
for (const element of copyOfActiveElementList) {
|
||||
|
@ -13,9 +13,10 @@ export const slides: Slide[] = [
|
||||
height: 362.5,
|
||||
viewBox: 200,
|
||||
path: 'M 0 0 L 0 200 L 200 200 Z',
|
||||
fill: '#d14424',
|
||||
fill: '#5b9bd5',
|
||||
fixedRatio: false,
|
||||
opacity: 0.7,
|
||||
rotate: 0
|
||||
},
|
||||
{
|
||||
type: 'shape',
|
||||
@ -26,12 +27,13 @@ export const slides: Slide[] = [
|
||||
height: 320,
|
||||
viewBox: 200,
|
||||
path: 'M 0 0 L 0 200 L 200 200 Z',
|
||||
fill: '#d14424',
|
||||
fill: '#5b9bd5',
|
||||
fixedRatio: false,
|
||||
flip: {
|
||||
x: 180,
|
||||
y: 0,
|
||||
},
|
||||
rotate: 0
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
@ -40,7 +42,9 @@ export const slides: Slide[] = [
|
||||
top: 65.25,
|
||||
width: 585,
|
||||
height: 188,
|
||||
lineHeight: 1.2,
|
||||
content: '<p style=\'\'><strong><span style=\'font-size: 112px\'>PPTIST</span></strong></p>',
|
||||
rotate: 0
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
@ -50,6 +54,7 @@ export const slides: Slide[] = [
|
||||
width: 585,
|
||||
height: 56,
|
||||
content: '<p style=\'\'><span style=\'font-size: 24px\'>基于 Vue 3.x + TypeScript 的在线演示文稿应用</span></p>',
|
||||
rotate: 0
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
@ -59,7 +64,7 @@ export const slides: Slide[] = [
|
||||
start: [0, 0],
|
||||
end: [549, 0],
|
||||
points: ['', ''],
|
||||
color: '#d14424',
|
||||
color: '#5b9bd5',
|
||||
style: 'solid',
|
||||
width: 2,
|
||||
},
|
8
src/mocks/theme.ts
Normal file
8
src/mocks/theme.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { SlideTheme } from '@/types/slides'
|
||||
|
||||
export const theme: SlideTheme = {
|
||||
themeColor: '#5b9bd5',
|
||||
fontColor: '#333',
|
||||
fontName: '微软雅黑',
|
||||
backgroundColor: '#fff',
|
||||
}
|
@ -69,6 +69,7 @@ import {
|
||||
Platte,
|
||||
UpOne,
|
||||
DownOne,
|
||||
Close,
|
||||
CloseSmall,
|
||||
Undo,
|
||||
Transform,
|
||||
@ -76,6 +77,9 @@ import {
|
||||
Theme,
|
||||
ArrowCircleLeft,
|
||||
GraphicDesign,
|
||||
Logout,
|
||||
Erase,
|
||||
Clear,
|
||||
} from '@icon-park/vue-next'
|
||||
|
||||
export default {
|
||||
@ -154,6 +158,7 @@ export default {
|
||||
app.component('IconRightTwo', RightTwo)
|
||||
app.component('IconPlus', Plus)
|
||||
app.component('IconMinus', Minus)
|
||||
app.component('IconClose', Close)
|
||||
app.component('IconCloseSmall', CloseSmall)
|
||||
|
||||
// 图表
|
||||
@ -171,6 +176,7 @@ export default {
|
||||
app.component('IconHelpcenter', Helpcenter)
|
||||
app.component('IconGithub', Github)
|
||||
app.component('IconWrite', Write)
|
||||
app.component('IconErase', Erase)
|
||||
app.component('IconEffects', Effects)
|
||||
app.component('IconRotate', Rotate)
|
||||
app.component('IconEdit', Edit)
|
||||
@ -179,5 +185,7 @@ export default {
|
||||
app.component('IconClick', Click)
|
||||
app.component('IconTheme', Theme)
|
||||
app.component('IconArrowCircleLeft', ArrowCircleLeft)
|
||||
app.component('IconLogout', Logout)
|
||||
app.component('IconClear', Clear)
|
||||
}
|
||||
}
|
@ -23,26 +23,38 @@ export const actions: ActionTree<State, State> = {
|
||||
},
|
||||
|
||||
async [ActionTypes.ADD_SNAPSHOT]({ state, commit }) {
|
||||
|
||||
// 获取当前indexeddb中全部快照的ID
|
||||
const allKeys = await snapshotDB.snapshots.orderBy('id').keys()
|
||||
|
||||
let needDeleteKeys: IndexableTypeArray = []
|
||||
|
||||
// 记录需要删除的快照ID
|
||||
// 若当前快照指针不处在最后一位,那么再添加快照时,应该将当前指针位置后面的快照全部删除,对应的实际情况是:
|
||||
// 用户撤回多次后,再进行操作(添加快照),此时原先被撤销的快照都应该被删除
|
||||
if (state.snapshotCursor >= 0 && state.snapshotCursor < allKeys.length - 1) {
|
||||
needDeleteKeys = allKeys.slice(state.snapshotCursor + 1)
|
||||
}
|
||||
|
||||
// 添加新快照
|
||||
const snapshot = {
|
||||
index: state.slideIndex,
|
||||
slides: state.slides,
|
||||
}
|
||||
await snapshotDB.snapshots.add(snapshot)
|
||||
|
||||
// 计算当前快照长度,用于设置快照指针的位置(此时指针应该处在最后一位,即:快照长度 - 1)
|
||||
let snapshotLength = allKeys.length - needDeleteKeys.length + 1
|
||||
|
||||
if (snapshotLength > 20) {
|
||||
// 快照数量超过长度限制时,应该将头部多余的快照删除
|
||||
const snapshotLengthLimit = 20
|
||||
if (snapshotLength > snapshotLengthLimit) {
|
||||
needDeleteKeys.push(allKeys[0])
|
||||
snapshotLength--
|
||||
}
|
||||
|
||||
// 快照数大于1时,需要保证撤回操作后维持页面焦点不变:也就是将倒数第二个快照对应的索引设置为当前页的索引
|
||||
// https://github.com/pipipi-pikachu/PPTist/issues/27
|
||||
if (snapshotLength >= 2) {
|
||||
snapshotDB.snapshots.update(allKeys[snapshotLength - 2] as number, { index: state.slideIndex })
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ export const enum MutationTypes {
|
||||
// editor
|
||||
SET_ACTIVE_ELEMENT_ID_LIST = 'setActiveElementIdList',
|
||||
SET_HANDLE_ELEMENT_ID = 'setHandleElementId',
|
||||
SET_ACTIVE_GROUP_ELEMENT_ID = 'setActiveGroupElementId',
|
||||
SET_CANVAS_PERCENTAGE = 'setCanvasPercentage',
|
||||
SET_CANVAS_SCALE = 'setCanvasScale',
|
||||
SET_THUMBNAILS_FOCUS = 'setThumbnailsFocus',
|
||||
|
@ -31,6 +31,10 @@ export const mutations: MutationTree<State> = {
|
||||
[MutationTypes.SET_HANDLE_ELEMENT_ID](state, handleElementId: string) {
|
||||
state.handleElementId = handleElementId
|
||||
},
|
||||
|
||||
[MutationTypes.SET_ACTIVE_GROUP_ELEMENT_ID](state, activeGroupElementId: string) {
|
||||
state.activeGroupElementId = activeGroupElementId
|
||||
},
|
||||
|
||||
[MutationTypes.SET_CANVAS_PERCENTAGE](state, percentage: number) {
|
||||
state.canvasPercentage = percentage
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { Slide, SlideTheme } from '@/types/slides'
|
||||
import { CreatingElement } from '@/types/edit'
|
||||
import { ToolbarState } from '@/types/toolbar'
|
||||
import { slides } from '@/mocks/index'
|
||||
import { slides } from '@/mocks/slides'
|
||||
import { theme } from '@/mocks/theme'
|
||||
import { SYS_FONTS } from '@/configs/font'
|
||||
|
||||
export interface State {
|
||||
activeElementIdList: string[];
|
||||
handleElementId: string;
|
||||
activeGroupElementId: string;
|
||||
canvasPercentage: number;
|
||||
canvasScale: number;
|
||||
thumbnailsFocus: boolean;
|
||||
@ -30,31 +32,27 @@ export interface State {
|
||||
}
|
||||
|
||||
export const state: State = {
|
||||
activeElementIdList: [],
|
||||
handleElementId: '',
|
||||
canvasPercentage: 90,
|
||||
canvasScale: 1,
|
||||
thumbnailsFocus: false,
|
||||
editorAreaFocus: false,
|
||||
disableHotkeys: false,
|
||||
showGridLines: false,
|
||||
creatingElement: null,
|
||||
availableFonts: [],
|
||||
toolbarState: 'slideStyle',
|
||||
theme: {
|
||||
themeColor: '#d14424',
|
||||
fontColor: '#333',
|
||||
fontName: '微软雅黑',
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
viewportRatio: 0.5625,
|
||||
slides: slides,
|
||||
slideIndex: 0,
|
||||
selectedSlidesIndex: [],
|
||||
snapshotCursor: -1,
|
||||
snapshotLength: 0,
|
||||
ctrlKeyState: false,
|
||||
shiftKeyState: false,
|
||||
screening: false,
|
||||
clipingImageElementId: '',
|
||||
activeElementIdList: [], // 被选中的元素ID集合,包含 handleElementId
|
||||
handleElementId: '', // 正在操作的元素ID
|
||||
activeGroupElementId: '', // 组合元素成员中,被选中可独立操作的元素ID
|
||||
canvasPercentage: 90, // 画布可视区域百分比
|
||||
canvasScale: 1, // 画布缩放比例(基于宽度1000px)
|
||||
thumbnailsFocus: false, // 左侧导航缩略图区域聚焦
|
||||
editorAreaFocus: false, // 编辑区域聚焦
|
||||
disableHotkeys: false, // 禁用快捷键
|
||||
showGridLines: false, // 显示网格线
|
||||
creatingElement: null, // 正在插入的元素信息,需要绘制插入的元素需要(文字、形状、线条)
|
||||
availableFonts: [], // 当前环境可用字体
|
||||
toolbarState: 'slideStyle', // 右侧工具栏状态
|
||||
viewportRatio: 0.5625, // 可是区域比例,默认16:9
|
||||
theme: theme, // 主题样式
|
||||
slides: slides, // 幻灯片页面数据
|
||||
slideIndex: 0, // 当前页面索引
|
||||
selectedSlidesIndex: [], // 当前被选中的页面索引集合
|
||||
snapshotCursor: -1, // 历史快照指针
|
||||
snapshotLength: 0, // 历史快照长度
|
||||
ctrlKeyState: false, // ctrl键按下状态
|
||||
shiftKeyState: false, // shift键按下状态
|
||||
screening: false, // 是否进入放映状态
|
||||
clipingImageElementId: '', // 当前正在裁剪的图片ID
|
||||
}
|
@ -32,10 +32,10 @@ interface PPTBaseElement {
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface PPTTextElement extends PPTBaseElement{
|
||||
export interface PPTTextElement extends PPTBaseElement {
|
||||
type: 'text';
|
||||
content: string;
|
||||
rotate?: number;
|
||||
rotate: number;
|
||||
outline?: PPTElementOutline;
|
||||
fill?: string;
|
||||
lineHeight?: number;
|
||||
@ -61,11 +61,11 @@ export interface ImageElementClip {
|
||||
range: [[number, number], [number, number]];
|
||||
shape: string;
|
||||
}
|
||||
export interface PPTImageElement extends PPTBaseElement{
|
||||
export interface PPTImageElement extends PPTBaseElement {
|
||||
type: 'image';
|
||||
fixedRatio: boolean;
|
||||
src: string;
|
||||
rotate?: number;
|
||||
rotate: number;
|
||||
outline?: PPTElementOutline;
|
||||
filters?: ImageElementFilters;
|
||||
clip?: ImageElementClip;
|
||||
@ -78,21 +78,21 @@ export interface ShapeGradient {
|
||||
color: [string, string];
|
||||
rotate: number;
|
||||
}
|
||||
export interface PPTShapeElement extends PPTBaseElement{
|
||||
export interface PPTShapeElement extends PPTBaseElement {
|
||||
type: 'shape';
|
||||
viewBox: number;
|
||||
path: string;
|
||||
fixedRatio: boolean;
|
||||
fill: string;
|
||||
gradient?: ShapeGradient;
|
||||
rotate?: number;
|
||||
rotate: number;
|
||||
outline?: PPTElementOutline;
|
||||
opacity?: number;
|
||||
flip?: ImageOrShapeFlip;
|
||||
shadow?: PPTElementShadow;
|
||||
}
|
||||
|
||||
export interface PPTLineElement extends Omit<PPTBaseElement, 'height'>{
|
||||
export interface PPTLineElement extends Omit<PPTBaseElement, 'height'> {
|
||||
type: 'line';
|
||||
start: [number, number];
|
||||
end: [number, number];
|
||||
@ -109,7 +109,7 @@ export interface ChartData {
|
||||
labels: string[];
|
||||
series: number[][];
|
||||
}
|
||||
export interface PPTChartElement extends PPTBaseElement{
|
||||
export interface PPTChartElement extends PPTBaseElement {
|
||||
type: 'chart';
|
||||
fill?: string;
|
||||
chartType: ChartType;
|
||||
@ -145,7 +145,7 @@ export interface TableTheme {
|
||||
colHeader: boolean;
|
||||
colFooter: boolean;
|
||||
}
|
||||
export interface PPTTableElement extends PPTBaseElement{
|
||||
export interface PPTTableElement extends PPTBaseElement {
|
||||
type: 'table';
|
||||
outline: PPTElementOutline;
|
||||
theme?: TableTheme;
|
||||
|
@ -30,16 +30,9 @@ export default defineComponent({
|
||||
|
||||
// 计算网格线的颜色,避免与背景的颜色太接近
|
||||
const gridColor = computed(() => {
|
||||
if (!background.value || background.value.type === 'image') return 'rgba(100, 100, 100, 0.5)'
|
||||
const color = background.value.color
|
||||
const rgba = tinycolor(color).toRgb()
|
||||
const newRgba = {
|
||||
r: rgba.r > 128 ? rgba.r - 128 : rgba.r + 127,
|
||||
g: rgba.g > 128 ? rgba.g - 128 : rgba.g + 127,
|
||||
b: rgba.b > 128 ? rgba.b - 128 : rgba.b + 127,
|
||||
a: 0.5
|
||||
}
|
||||
return `rgba(${[newRgba.r, newRgba.g, newRgba.b, newRgba.a].join(',')})`
|
||||
const bgColor = background.value?.color || '#fff'
|
||||
const colorList = ['#000', '#fff']
|
||||
return tinycolor.mostReadable(bgColor, colorList, { includeFallbackColors: true }).setAlpha(.5).toRgbString()
|
||||
})
|
||||
|
||||
const gridSize = 50
|
||||
@ -80,5 +73,7 @@ export default defineComponent({
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
overflow: visible;
|
||||
z-index: 999;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
@ -8,11 +8,11 @@ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||
|
||||
export default (
|
||||
elementList: Ref<PPTElement[]>,
|
||||
activeGroupElementId: Ref<string>,
|
||||
alignmentLines: Ref<AlignmentLineProps[]>,
|
||||
) => {
|
||||
const store = useStore()
|
||||
const activeElementIdList = computed(() => store.state.activeElementIdList)
|
||||
const activeGroupElementId = computed(() => store.state.activeGroupElementId)
|
||||
const canvasScale = computed(() => store.state.canvasScale)
|
||||
const viewportRatio = computed(() => store.state.viewportRatio)
|
||||
|
||||
|
@ -94,11 +94,11 @@ const getOppositePoint = (direction: string, points: ReturnType<typeof getRotate
|
||||
|
||||
export default (
|
||||
elementList: Ref<PPTElement[]>,
|
||||
activeGroupElementId: Ref<string>,
|
||||
alignmentLines: Ref<AlignmentLineProps[]>,
|
||||
) => {
|
||||
const store = useStore()
|
||||
const activeElementIdList = computed(() => store.state.activeElementIdList)
|
||||
const activeGroupElementId = computed(() => store.state.activeGroupElementId)
|
||||
const canvasScale = computed(() => store.state.canvasScale)
|
||||
const viewportRatio = computed(() => store.state.viewportRatio)
|
||||
const ctrlOrShiftKeyActive = computed<boolean>(() => store.getters.ctrlOrShiftKeyActive)
|
||||
|
@ -5,21 +5,22 @@ import { PPTElement } from '@/types/slides'
|
||||
|
||||
export default (
|
||||
elementList: Ref<PPTElement[]>,
|
||||
activeGroupElementId: Ref<string>,
|
||||
moveElement: (e: MouseEvent, element: PPTElement) => void,
|
||||
) => {
|
||||
const store = useStore()
|
||||
const activeElementIdList = computed(() => store.state.activeElementIdList)
|
||||
const handleElementId = computed(() => store.state.handleElementId)
|
||||
const activeGroupElementId = computed(() => store.state.activeGroupElementId)
|
||||
const editorAreaFocus = computed(() => store.state.editorAreaFocus)
|
||||
const ctrlOrShiftKeyActive = computed<boolean>(() => store.getters.ctrlOrShiftKeyActive)
|
||||
|
||||
// 选中元素
|
||||
const selectElement = (e: MouseEvent, element: PPTElement, canMove = true) => {
|
||||
// startMove 表示是否需要再选中操作后进入到开始移动的状态
|
||||
const selectElement = (e: MouseEvent, element: PPTElement, startMove = true) => {
|
||||
if (!editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, true)
|
||||
|
||||
// 如果目标元素当前未被选中,则将他设为选中状态
|
||||
// 此时如果按下Ctrl键或Shift键,则进入多选状态,将当前已选中的元素和目标元素一桶设置为选中状态,否则仅将目标元素设置为选中状态
|
||||
// 此时如果按下Ctrl键或Shift键,则进入多选状态,将当前已选中的元素和目标元素一起设置为选中状态,否则仅将目标元素设置为选中状态
|
||||
// 如果目标元素是分组成员,需要将该组合的其他元素一起设置为选中状态
|
||||
if (!activeElementIdList.value.includes(element.id)) {
|
||||
let newActiveIdList: string[] = []
|
||||
@ -78,13 +79,13 @@ export default (
|
||||
const currentPageY = e.pageY
|
||||
|
||||
if (startPageX === currentPageX && startPageY === currentPageY) {
|
||||
activeGroupElementId.value = element.id
|
||||
store.commit(MutationTypes.SET_ACTIVE_GROUP_ELEMENT_ID, element.id)
|
||||
;(e.target as HTMLElement).onmouseup = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (canMove) moveElement(e, element)
|
||||
if (startMove) moveElement(e, element)
|
||||
}
|
||||
|
||||
// 选中页面内的全部元素
|
||||
@ -98,4 +99,4 @@ export default (
|
||||
selectElement,
|
||||
selectAllElement,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div
|
||||
class="canvas"
|
||||
ref="canvasRef"
|
||||
@mousewheel="$event => mousewheelScaleCanvas($event)"
|
||||
@mousewheel="$event => handleMousewheelCanvas($event)"
|
||||
@mousedown="$event => handleClickBlankArea($event)"
|
||||
v-contextmenu="contextmenus"
|
||||
v-click-outside="removeEditorAreaFocus"
|
||||
@ -82,6 +82,7 @@ import { ContextmenuItem } from '@/components/Contextmenu/types'
|
||||
import { PPTElement, Slide } from '@/types/slides'
|
||||
import { AlignmentLineProps } from '@/types/edit'
|
||||
import { removeAllRanges } from '@/utils/selection'
|
||||
import { KEYS } from '@/configs/hotkey'
|
||||
|
||||
import useViewportSize from './hooks/useViewportSize'
|
||||
import useMouseSelection from './hooks/useMouseSelection'
|
||||
@ -98,6 +99,7 @@ import useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'
|
||||
import useSelectAllElement from '@/hooks/useSelectAllElement'
|
||||
import useScaleCanvas from '@/hooks/useScaleCanvas'
|
||||
import useScreening from '@/hooks/useScreening'
|
||||
import useSlideHandler from '@/hooks/useSlideHandler'
|
||||
|
||||
import EditableElement from './EditableElement.vue'
|
||||
import MouseSelection from './MouseSelection.vue'
|
||||
@ -123,6 +125,7 @@ export default defineComponent({
|
||||
|
||||
const activeElementIdList = computed(() => store.state.activeElementIdList)
|
||||
const handleElementId = computed(() => store.state.handleElementId)
|
||||
const activeGroupElementId = computed(() => store.state.activeGroupElementId)
|
||||
const editorAreaFocus = computed(() => store.state.editorAreaFocus)
|
||||
const ctrlKeyState = computed(() => store.state.ctrlKeyState)
|
||||
const ctrlOrShiftKeyActive = computed<boolean>(() => store.getters.ctrlOrShiftKeyActive)
|
||||
@ -130,8 +133,9 @@ export default defineComponent({
|
||||
const viewportRef = ref<HTMLElement>()
|
||||
const alignmentLines = ref<AlignmentLineProps[]>([])
|
||||
|
||||
const activeGroupElementId = ref('')
|
||||
watch(handleElementId, () => activeGroupElementId.value = '')
|
||||
watch(handleElementId, () => {
|
||||
store.commit(MutationTypes.SET_ACTIVE_GROUP_ELEMENT_ID, '')
|
||||
})
|
||||
|
||||
const currentSlide = computed<Slide>(() => store.getters.currentSlide)
|
||||
const elementList = ref<PPTElement[]>([])
|
||||
@ -148,16 +152,17 @@ export default defineComponent({
|
||||
|
||||
const { mouseSelectionState, updateMouseSelection } = useMouseSelection(elementList, viewportRef)
|
||||
|
||||
const { dragElement } = useDragElement(elementList, activeGroupElementId, alignmentLines)
|
||||
const { dragElement } = useDragElement(elementList, alignmentLines)
|
||||
const { dragLineElement } = useDragLineElement(elementList)
|
||||
const { selectElement } = useSelectElement(elementList, activeGroupElementId, dragElement)
|
||||
const { scaleElement, scaleMultiElement } = useScaleElement(elementList, activeGroupElementId, alignmentLines)
|
||||
const { selectElement } = useSelectElement(elementList, dragElement)
|
||||
const { scaleElement, scaleMultiElement } = useScaleElement(elementList, alignmentLines)
|
||||
const { rotateElement } = useRotateElement(elementList, viewportRef)
|
||||
|
||||
const { selectAllElement } = useSelectAllElement()
|
||||
const { deleteAllElements } = useDeleteElement()
|
||||
const { pasteElement } = useCopyAndPasteElement()
|
||||
const { enterScreening } = useScreening()
|
||||
const { updateSlideIndex } = useSlideHandler()
|
||||
|
||||
// 点击画布的空白区域:清空焦点元素、设置画布焦点、清除文字选区
|
||||
const handleClickBlankArea = (e: MouseEvent) => {
|
||||
@ -172,16 +177,24 @@ export default defineComponent({
|
||||
if (editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, false)
|
||||
}
|
||||
|
||||
// 按住Ctrl键滚动鼠标缩放画布
|
||||
// 滚动鼠标
|
||||
const { scaleCanvas } = useScaleCanvas()
|
||||
const throttleScaleCanvas = throttle(scaleCanvas, 100, { leading: true, trailing: false })
|
||||
const throttleUpdateSlideIndex = throttle(updateSlideIndex, 300, { leading: true, trailing: false })
|
||||
|
||||
const mousewheelScaleCanvas = (e: WheelEvent) => {
|
||||
if (!ctrlKeyState.value) return
|
||||
|
||||
const handleMousewheelCanvas = (e: WheelEvent) => {
|
||||
e.preventDefault()
|
||||
if (e.deltaY > 0) throttleScaleCanvas('-')
|
||||
else if (e.deltaY < 0) throttleScaleCanvas('+')
|
||||
|
||||
// 按住Ctrl键时:缩放画布
|
||||
if (ctrlKeyState.value) {
|
||||
if (e.deltaY > 0) throttleScaleCanvas('-')
|
||||
else if (e.deltaY < 0) throttleScaleCanvas('+')
|
||||
}
|
||||
// 上下翻页
|
||||
else {
|
||||
if (e.deltaY > 0) throttleUpdateSlideIndex(KEYS.DOWN)
|
||||
else if (e.deltaY < 0) throttleUpdateSlideIndex(KEYS.UP)
|
||||
}
|
||||
}
|
||||
|
||||
// 开关网格线
|
||||
@ -247,7 +260,7 @@ export default defineComponent({
|
||||
scaleElement,
|
||||
dragLineElement,
|
||||
scaleMultiElement,
|
||||
mousewheelScaleCanvas,
|
||||
handleMousewheelCanvas,
|
||||
contextmenus,
|
||||
}
|
||||
},
|
||||
|
@ -186,6 +186,7 @@ export default defineComponent({
|
||||
}
|
||||
.handler-item {
|
||||
margin: 0 10px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
|
||||
&.disable {
|
||||
@ -202,8 +203,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
.viewport-size {
|
||||
font-size: 12px;
|
||||
margin-top: -1px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,110 +1,35 @@
|
||||
<template>
|
||||
<div class="export-dialog">
|
||||
<div class="tabs">
|
||||
<div
|
||||
class="tab"
|
||||
:class="{ 'active': tab.value === currentTab }"
|
||||
v-for="tab in tabs"
|
||||
:key="tab.value"
|
||||
@click="currentTab = tab.value"
|
||||
>{{tab.label}}</div>
|
||||
<div class="preview">
|
||||
<pre>{{slides}}</pre>
|
||||
</div>
|
||||
|
||||
<div class="content json" v-if="currentTab === 'json'">
|
||||
<div class="json-preview">
|
||||
<pre>{{slides}}</pre>
|
||||
</div>
|
||||
<div class="json-configs">
|
||||
<Button class="btn" type="primary" @click="exportJSON()">导出 JSON 文件</Button>
|
||||
<Button class="btn" @click="emit('close')">关闭</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content image" v-if="currentTab === 'image'">
|
||||
<div class="thumbnails-view">
|
||||
<div class="thumbnails" ref="imageThumbnailsRef">
|
||||
<ThumbnailSlide
|
||||
class="thumbnail"
|
||||
v-for="slide in slides"
|
||||
:key="slide.id"
|
||||
:slide="slide"
|
||||
:size="1600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="configs">
|
||||
<Button class="btn" type="primary" @click="exportImage('png')">导出 PNG 图片</Button>
|
||||
<Button class="btn" type="primary" @click="exportImage('jpeg')">导出 JPEG 图片</Button>
|
||||
<Button class="btn" @click="emit('close')">关闭</Button>
|
||||
</div>
|
||||
<div class="spinning" v-if="spinning">
|
||||
<Spin />
|
||||
<div class="tip">正在导出,请稍等...</div>
|
||||
</div>
|
||||
<div class="handle">
|
||||
<Button class="btn" type="primary" @click="exportJSON()">导出</Button>
|
||||
<Button class="btn" @click="emit('close')">关闭</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref } from 'vue'
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import { useStore } from '@/store'
|
||||
import { saveAs } from 'file-saver'
|
||||
import { toPng, toJpeg } from 'html-to-image'
|
||||
|
||||
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'export-dialog',
|
||||
components: {
|
||||
ThumbnailSlide,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const store = useStore()
|
||||
const slides = computed(() => store.state.slides)
|
||||
|
||||
const tabs = ref([
|
||||
{ label: 'JSON', value: 'json' },
|
||||
{ label: '图片', value: 'image' },
|
||||
])
|
||||
|
||||
const currentTab = ref('json')
|
||||
const spinning = ref(false)
|
||||
|
||||
const exportJSON = () => {
|
||||
const blob = new Blob([JSON.stringify(slides.value)], { type: '' })
|
||||
saveAs(blob, 'pptist_slides.json')
|
||||
}
|
||||
|
||||
const imageThumbnailsRef = ref<HTMLElement>()
|
||||
const exportImage = (type: string) => {
|
||||
spinning.value = true
|
||||
const toImage = type === 'png' ? toPng : toJpeg
|
||||
|
||||
setTimeout(() => {
|
||||
if (!imageThumbnailsRef.value) return
|
||||
|
||||
toImage(imageThumbnailsRef.value, {
|
||||
quality: 0.95,
|
||||
width: 1600,
|
||||
}).then(dataUrl => {
|
||||
spinning.value = false
|
||||
saveAs(dataUrl, `pptist_slides.${type}`)
|
||||
}).catch(() => {
|
||||
spinning.value = false
|
||||
message.error('导出图片失败')
|
||||
})
|
||||
}, 200)
|
||||
}
|
||||
|
||||
return {
|
||||
tabs,
|
||||
currentTab,
|
||||
spinning,
|
||||
slides,
|
||||
exportJSON,
|
||||
exportImage,
|
||||
imageThumbnailsRef,
|
||||
emit,
|
||||
}
|
||||
},
|
||||
@ -114,40 +39,11 @@ export default defineComponent({
|
||||
<style lang="scss" scoped>
|
||||
.export-dialog {
|
||||
height: 500px;
|
||||
}
|
||||
.tabs {
|
||||
height: 40px;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
margin: -24px -24px 20px -24px;
|
||||
}
|
||||
.tab {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: $lightGray;
|
||||
border-bottom: 1px solid $borderColor;
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
background-color: #fff;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
|
||||
& + .tab {
|
||||
border-left: 1px solid $borderColor;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
height: calc(100% - 60px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.json-preview {
|
||||
.preview {
|
||||
width: 460px;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
@ -160,7 +56,7 @@ export default defineComponent({
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.json-configs {
|
||||
.handle {
|
||||
flex: 1;
|
||||
|
||||
.btn {
|
||||
@ -168,52 +64,4 @@ export default defineComponent({
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.thumbnails-view {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.configs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.btn {
|
||||
width: 240px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.spinning {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
|
||||
.tip {
|
||||
margin-top: 10px;
|
||||
color: $themeColor;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -11,7 +11,7 @@
|
||||
<MenuItem @click="deleteSlide()">删除页面</MenuItem>
|
||||
<MenuItem @click="toggleGridLines()">{{ showGridLines ? '关闭网格线' : '打开网格线' }}</MenuItem>
|
||||
<MenuItem @click="resetSlides()">重置幻灯片</MenuItem>
|
||||
<MenuItem @click="exportDialogVisible = true">导出为</MenuItem>
|
||||
<MenuItem @click="exportDialogVisible = true">导出 JSON</MenuItem>
|
||||
</Menu>
|
||||
</template>
|
||||
</Dropdown>
|
||||
@ -28,7 +28,6 @@
|
||||
<div class="menu-item"><IconHelpcenter /> <span class="text">帮助</span></div>
|
||||
<template #overlay>
|
||||
<Menu>
|
||||
<MenuItem @click="openDoc()">开发文档</MenuItem>
|
||||
<MenuItem @click="hotkeyDrawerVisible = true">快捷键</MenuItem>
|
||||
</Menu>
|
||||
</template>
|
||||
@ -78,8 +77,6 @@ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||
import HotkeyDoc from './HotkeyDoc.vue'
|
||||
import ExportDialog from './ExportDialog.vue'
|
||||
|
||||
import { message } from 'ant-design-vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'editor-header',
|
||||
components: {
|
||||
@ -98,10 +95,6 @@ export default defineComponent({
|
||||
store.commit(MutationTypes.SET_GRID_LINES_STATE, !showGridLines.value)
|
||||
}
|
||||
|
||||
const openDoc = () => {
|
||||
message.warning('作者努力编写中...')
|
||||
}
|
||||
|
||||
const hotkeyDrawerVisible = ref(false)
|
||||
const exportDialogVisible = ref(false)
|
||||
|
||||
@ -115,7 +108,6 @@ export default defineComponent({
|
||||
toggleGridLines,
|
||||
showGridLines,
|
||||
resetSlides,
|
||||
openDoc,
|
||||
hotkeyDrawerVisible,
|
||||
exportDialogVisible,
|
||||
}
|
||||
@ -142,7 +134,7 @@ export default defineComponent({
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
padding: 0 10px;
|
||||
transition: background-color .2s;
|
||||
cursor: pointer;
|
||||
|
@ -5,7 +5,7 @@
|
||||
v-click-outside="() => setThumbnailsFocus(false)"
|
||||
v-contextmenu="contextmenusThumbnails"
|
||||
>
|
||||
<div class="add-slide" @click="createSlide()"><IconPlus /> 添加幻灯片</div>
|
||||
<div class="add-slide" @click="createSlide()"><IconPlus class="icon" />添加幻灯片</div>
|
||||
<Draggable
|
||||
class="thumbnail-list"
|
||||
:modelValue="slides"
|
||||
@ -22,7 +22,7 @@
|
||||
'active': slideIndex === index,
|
||||
'selected': selectedSlidesIndex.includes(index),
|
||||
}"
|
||||
@mousedown="handleClickSlideThumbnail(index)"
|
||||
@mousedown="$event => handleClickSlideThumbnail($event, index)"
|
||||
v-contextmenu="contextmenusThumbnailItem"
|
||||
>
|
||||
<div class="label">{{ fillDigit(index + 1, 2) }}</div>
|
||||
@ -77,9 +77,11 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
// 点击缩略图
|
||||
const handleClickSlideThumbnail = (index: number) => {
|
||||
const handleClickSlideThumbnail = (e: MouseEvent, index: number) => {
|
||||
const isMultiSelected = selectedSlidesIndex.value.length > 1
|
||||
|
||||
if (isMultiSelected && selectedSlidesIndex.value.includes(index) && e.button !== 0) return
|
||||
|
||||
// 按住Ctrl键,点选幻灯片,再次点击已选中的页面则取消选中
|
||||
if (ctrlKeyState.value) {
|
||||
if (slideIndex.value === index) {
|
||||
@ -255,6 +257,11 @@ export default defineComponent({
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid $borderColor;
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
margin-right: 3px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
.thumbnail-list {
|
||||
padding: 5px 0;
|
||||
|
@ -129,7 +129,7 @@ export default defineComponent({
|
||||
const themeColor = ref<string>('')
|
||||
const gridColor = ref('')
|
||||
|
||||
const lineSmooth = ref<boolean | Function>(true)
|
||||
const lineSmooth = ref(true)
|
||||
const showLine = ref(true)
|
||||
const showArea = ref(false)
|
||||
const horizontalBars = ref(false)
|
||||
@ -148,7 +148,7 @@ export default defineComponent({
|
||||
donut: _donut,
|
||||
} = handleElement.value.options
|
||||
|
||||
if (_lineSmooth !== undefined) lineSmooth.value = _lineSmooth
|
||||
if (_lineSmooth !== undefined) lineSmooth.value = _lineSmooth as boolean
|
||||
if (_showLine !== undefined) showLine.value = _showLine
|
||||
if (_showArea !== undefined) showArea.value = _showArea
|
||||
if (_horizontalBars !== undefined) horizontalBars.value = _horizontalBars
|
||||
|
@ -2,24 +2,24 @@
|
||||
<div class="multi-position-panel">
|
||||
<ButtonGroup class="row">
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="左对齐">
|
||||
<Button style="flex: 1;" @click="alignActiveElement('left')"><IconAlignLeft /></Button>
|
||||
<Button style="flex: 1;" @click="alignElement('left')"><IconAlignLeft /></Button>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="水平居中">
|
||||
<Button style="flex: 1;" @click="alignActiveElement('horizontal')"><IconAlignVertically /></Button>
|
||||
<Button style="flex: 1;" @click="alignElement('horizontal')"><IconAlignVertically /></Button>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="右对齐">
|
||||
<Button style="flex: 1;" @click="alignActiveElement('right')"><IconAlignRight /></Button>
|
||||
<Button style="flex: 1;" @click="alignElement('right')"><IconAlignRight /></Button>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup class="row">
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="上对齐">
|
||||
<Button style="flex: 1;" @click="alignActiveElement('top')"><IconAlignTop /></Button>
|
||||
<Button style="flex: 1;" @click="alignElement('top')"><IconAlignTop /></Button>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="垂直居中">
|
||||
<Button style="flex: 1;" @click="alignActiveElement('vertical')"><IconAlignHorizontally /></Button>
|
||||
<Button style="flex: 1;" @click="alignElement('vertical')"><IconAlignHorizontally /></Button>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="下对齐">
|
||||
<Button style="flex: 1;" @click="alignActiveElement('bottom')"><IconAlignBottom /></Button>
|
||||
<Button style="flex: 1;" @click="alignElement('bottom')"><IconAlignBottom /></Button>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup class="row" v-if="activeElementList.length > 2">
|
||||
@ -40,8 +40,10 @@
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import { useStore } from '@/store'
|
||||
import { PPTElement } from '@/types/slides'
|
||||
import { ElementAlignCommand } from '@/types/edit'
|
||||
import useCombineElement from '@/hooks/useCombineElement'
|
||||
import useAlignActiveElement from '@/hooks/useAlignActiveElement'
|
||||
import useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas'
|
||||
import useUniformDisplayElement from '@/hooks/useUniformDisplayElement'
|
||||
|
||||
export default defineComponent({
|
||||
@ -52,6 +54,7 @@ export default defineComponent({
|
||||
|
||||
const { combineElements, uncombineElements } = useCombineElement()
|
||||
const { alignActiveElement } = useAlignActiveElement()
|
||||
const { alignElementToCanvas } = useAlignElementToCanvas()
|
||||
const { uniformHorizontalDisplay, uniformVerticalDisplay } = useUniformDisplayElement()
|
||||
|
||||
// 判断当前多选的几个元素是否可以组合
|
||||
@ -63,14 +66,22 @@ export default defineComponent({
|
||||
return !inSameGroup
|
||||
})
|
||||
|
||||
// 多选元素对齐,需要先判断当前所选中的元素状态:
|
||||
// 如果所选元素为一组组合元素,则将它对齐到画布;
|
||||
// 如果所选元素不是组合元素或不止一组元素(即当前为可组合状态),则将这多个(多组)元素相互对齐。
|
||||
const alignElement = (command: ElementAlignCommand) => {
|
||||
if (canCombine.value) alignActiveElement(command)
|
||||
else alignElementToCanvas(command)
|
||||
}
|
||||
|
||||
return {
|
||||
activeElementList,
|
||||
canCombine,
|
||||
combineElements,
|
||||
uncombineElements,
|
||||
alignActiveElement,
|
||||
uniformHorizontalDisplay,
|
||||
uniformVerticalDisplay,
|
||||
alignElement,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -9,9 +9,15 @@
|
||||
</teleport>
|
||||
|
||||
<div class="tools">
|
||||
<div class="btn" :class="{ 'active': writingBoardModel === 'pen' }" @click="changePen()">画笔</div>
|
||||
<div class="btn" :class="{ 'active': writingBoardModel === 'eraser' }" @click="changeEraser()">橡皮</div>
|
||||
<div class="btn" @click="clearCanvas()">清除墨迹</div>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.3" title="画笔">
|
||||
<div class="btn" :class="{ 'active': writingBoardModel === 'pen' }" @click="changePen()"><IconWrite class="icon" /></div>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.3" title="橡皮擦">
|
||||
<div class="btn" :class="{ 'active': writingBoardModel === 'eraser' }" @click="changeEraser()"><IconErase class="icon" /></div>
|
||||
</Tooltip>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.3" title="清除墨迹">
|
||||
<div class="btn" @click="clearCanvas()"><IconClear class="icon" /></div>
|
||||
</Tooltip>
|
||||
<div class="colors">
|
||||
<div
|
||||
class="color"
|
||||
@ -22,7 +28,9 @@
|
||||
@click="changeColor(color)"
|
||||
></div>
|
||||
</div>
|
||||
<div class="btn" @click="closeWritingBoard()">退出画笔</div>
|
||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.3" title="关闭画笔">
|
||||
<div class="btn" @click="closeWritingBoard()"><IconClose class="icon" /></div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -91,41 +99,49 @@ export default defineComponent({
|
||||
.tools {
|
||||
height: 50px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
bottom: 5px;
|
||||
left: 5px;
|
||||
z-index: 11;
|
||||
padding: 12px;
|
||||
background-color: #fff;
|
||||
background-color: #eee;
|
||||
border-radius: $borderRadius;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.btn {
|
||||
padding: 6px 10px;
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover, &.active {
|
||||
background-color: rgba($color: $themeColor, $alpha: .2);
|
||||
&:hover {
|
||||
color: $themeColor;
|
||||
}
|
||||
&.active {
|
||||
background-color: rgba($color: $themeColor, $alpha: .5);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
.colors {
|
||||
display: flex;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.color {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
outline: 1px solid #ccc;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: $borderRadius;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
transform: scale(1.15);
|
||||
}
|
||||
&.active {
|
||||
outline: 2px solid $themeColor;
|
||||
transform: scale(1.3);
|
||||
}
|
||||
|
||||
& + .color {
|
||||
margin-left: 5px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -422,15 +422,9 @@ export default defineComponent({
|
||||
right: 8px;
|
||||
padding: 8px 12px;
|
||||
color: #666;
|
||||
border: 2px solid #acacac;
|
||||
background-color: rgba($color: #fff, $alpha: .6);
|
||||
background-color: #eee;
|
||||
border-radius: $borderRadius;
|
||||
z-index: 10;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border: 2px solid #333;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -7,7 +7,6 @@
|
||||
width: elementInfo.width + 'px',
|
||||
height: elementInfo.height + 'px',
|
||||
}"
|
||||
@mousedown="$event => handleSelectElement($event)"
|
||||
>
|
||||
<div
|
||||
class="element-content"
|
||||
@ -15,6 +14,7 @@
|
||||
backgroundColor: elementInfo.fill,
|
||||
}"
|
||||
v-contextmenu="contextmenus"
|
||||
@mousedown="$event => handleSelectElement($event)"
|
||||
>
|
||||
<ElementOutline
|
||||
:width="elementInfo.width"
|
||||
@ -79,7 +79,6 @@ export default defineComponent({
|
||||
<style lang="scss" scoped>
|
||||
.editable-element-chart {
|
||||
position: absolute;
|
||||
cursor: move;
|
||||
|
||||
&.lock .element-content {
|
||||
cursor: default;
|
||||
@ -90,5 +89,6 @@ export default defineComponent({
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
cursor: move;
|
||||
}
|
||||
</style>
|
||||
|
@ -8,7 +8,6 @@
|
||||
width: elementInfo.width + 'px',
|
||||
height: elementInfo.height + 'px',
|
||||
}"
|
||||
@mousedown="$event => handleSelectElement($event)"
|
||||
>
|
||||
<div
|
||||
class="rotate-wrapper"
|
||||
@ -28,11 +27,12 @@
|
||||
<div
|
||||
class="element-content"
|
||||
v-else
|
||||
v-contextmenu="contextmenus"
|
||||
:style="{
|
||||
filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
|
||||
transform: flipStyle,
|
||||
}"
|
||||
v-contextmenu="contextmenus"
|
||||
@mousedown="$event => handleSelectElement($event)"
|
||||
>
|
||||
<ImageOutline :elementInfo="elementInfo" />
|
||||
|
||||
|
@ -8,7 +8,6 @@
|
||||
width: elementInfo.width + 'px',
|
||||
height: elementInfo.height + 'px',
|
||||
}"
|
||||
@mousedown="$event => handleSelectElement($event)"
|
||||
>
|
||||
<div
|
||||
class="rotate-wrapper"
|
||||
@ -16,12 +15,13 @@
|
||||
>
|
||||
<div
|
||||
class="element-content"
|
||||
v-contextmenu="contextmenus"
|
||||
:style="{
|
||||
opacity: elementInfo.opacity,
|
||||
filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
|
||||
transform: flipStyle,
|
||||
}"
|
||||
v-contextmenu="contextmenus"
|
||||
@mousedown="$event => handleSelectElement($event)"
|
||||
>
|
||||
<SvgWrapper
|
||||
overflow="visible"
|
||||
@ -118,7 +118,6 @@ export default defineComponent({
|
||||
<style lang="scss" scoped>
|
||||
.editable-element-shape {
|
||||
position: absolute;
|
||||
cursor: move;
|
||||
|
||||
&.lock .element-content {
|
||||
cursor: default;
|
||||
@ -132,6 +131,7 @@ export default defineComponent({
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
cursor: move;
|
||||
|
||||
svg {
|
||||
transform-origin: 0 0;
|
||||
|
@ -103,6 +103,8 @@ export default defineComponent({
|
||||
const realHeightCache = ref(-1)
|
||||
|
||||
const scaleElementStateListener = (state: boolean) => {
|
||||
if (handleElementId.value !== props.elementInfo.id) return
|
||||
|
||||
isScaling.value = state
|
||||
|
||||
if (state) editable.value = false
|
||||
@ -190,7 +192,6 @@ export default defineComponent({
|
||||
<style lang="scss" scoped>
|
||||
.editable-element-table {
|
||||
position: absolute;
|
||||
cursor: move;
|
||||
|
||||
&.lock .element-content {
|
||||
cursor: default;
|
||||
@ -201,6 +202,7 @@ export default defineComponent({
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
cursor: move;
|
||||
}
|
||||
.table-mask {
|
||||
position: absolute;
|
||||
|
@ -8,7 +8,6 @@
|
||||
left: elementInfo.left + 'px',
|
||||
width: elementInfo.width + 'px',
|
||||
}"
|
||||
@mousedown="$event => handleSelectElement($event)"
|
||||
>
|
||||
<div
|
||||
class="rotate-wrapper"
|
||||
@ -24,6 +23,7 @@
|
||||
letterSpacing: (elementInfo.wordSpace || 0) + 'px',
|
||||
}"
|
||||
v-contextmenu="contextmenus"
|
||||
@mousedown="$event => handleSelectElement($event)"
|
||||
>
|
||||
<ElementOutline
|
||||
:width="elementInfo.width"
|
||||
@ -108,6 +108,8 @@ export default defineComponent({
|
||||
// 监听文本元素的尺寸变化,当高度变化时,更新高度到vuex
|
||||
// 如果高度变化时正处在缩放操作中,则等待缩放操作结束后再更新
|
||||
const scaleElementStateListener = (state: boolean) => {
|
||||
if (handleElementId.value !== props.elementInfo.id) return
|
||||
|
||||
isScaling.value = state
|
||||
|
||||
if (!state && realHeightCache.value !== -1) {
|
||||
@ -323,7 +325,6 @@ export default defineComponent({
|
||||
<style lang="scss" scoped>
|
||||
.editable-element-text {
|
||||
position: absolute;
|
||||
cursor: move;
|
||||
|
||||
&.lock .element-content {
|
||||
cursor: default;
|
||||
@ -338,6 +339,7 @@ export default defineComponent({
|
||||
padding: 10px;
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
cursor: move;
|
||||
|
||||
.text {
|
||||
position: relative;
|
||||
|
Loading…
x
Reference in New Issue
Block a user