Merge branch 'master' into fix_reset

This commit is contained in:
LLzzZZ 2021-05-31 21:00:14 +08:00 committed by GitHub
commit 8c0c290102
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
71 changed files with 1194 additions and 1216 deletions

View File

@ -66,6 +66,7 @@ module.exports = {
'no-alert': isProduction ? 'error' : 'warn', 'no-alert': isProduction ? 'error' : 'warn',
'no-console': isProduction ? 'error' : 'warn', 'no-console': isProduction ? 'error' : 'warn',
'no-debugger': isProduction ? 'error' : 'warn', 'no-debugger': isProduction ? 'error' : 'warn',
'@typescript-eslint/explicit-module-boundary-types': 'off',
}, },
overrides: [ overrides: [
{ {

17
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View 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
View 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
View File

@ -1,6 +1,6 @@
.DS_Store .DS_Store
node_modules node_modules
/dist
# local env files # local env files
.env.local .env.local

100
README.md
View File

@ -1,11 +1,11 @@
# 🎨 PPTist # 🎨 PPTist
> 一个基于 Vue3.x + TypeScript 的在线演示文稿应用还原了大部分PPT常用功能支持 文字、图片、形状、线条、图表、表格 6种最常用的元素类型每一种元素都拥有高度可编辑能力同时支持丰富的快捷键和右键菜单尽可能还原本地桌面应用的使用体验。 > 一个基于 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快捷键没有作用** **Q. 为什么xxx快捷键没有作用**
@ -128,32 +121,55 @@ A. 由于本演示项目不依赖后端插入本地图片实际引用的是Ba
A. 设置预置主题的作用是使新添加的元素和页面应用主题样式,不会对已有的元素和页面生效(字体颜色除外),您可以使用“应用主题到全部”功能,将当前主题应用到当前全部页面中。 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的拖拽插件用于调整页面顺序等 > 作为一个在线幻灯片导出、导入PPTX文件是非常重要的功能。但是经过本人一段时间的调研发现该功能实现起来的复杂度远超过了预期。由于个人时间有限暂时可能无法集中精力来做该功能短时间内还是会更多优先去提升其他方面的使用体验。如果有做过相关内容的朋友也欢迎给我提建议。
`crypto-js` -- 加密函数库,用于加解密剪贴板内容
`clipboard` -- 用于复制内容到剪贴板
`icon-park` -- 图标库
# 💻 贡献代码 # 💻 贡献代码
@ -169,12 +185,12 @@ A. 设置预置主题的作用是使新添加的元素和页面应用主题样
- 对于较大的新功能,你需要先提交 Issues例如 ‘添加 XXX 功能’,确认该功能有被添加的必要后,再开始工作; - 对于较大的新功能,你需要先提交 Issues例如 ‘添加 XXX 功能’,确认该功能有被添加的必要后,再开始工作;
- 其他如简单的体验或代码优化、文档修正等,只要修改合理都会被接受。 - 其他如简单的体验或代码优化、文档修正等,只要修改合理都会被接受。
### 最后,感谢每一位贡献者: 在此,感谢每一位贡献者。
- [lhyUnited](https://github.com/lhyUnited)
# 📄 开源协议 # 📄 开源协议
[MIT License](https://github.com/pipipi-pikachu/PPTist/blob/master/LICENSE) [MIT License](https://github.com/pipipi-pikachu/PPTist/blob/master/LICENSE)
# 💣 友情提示 # 💣 友情提示
本项目不接受任何形式的私人咨询,有任何问题欢迎在 github 提交你的 Issues 本项目不接受任何形式的私人咨询,有任何问题 [欢迎在 github 提交你的 Issues](https://github.com/pipipi-pikachu/PPTist/issues)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
dist/favicon.ico vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
dist/index.html vendored
View File

@ -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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

490
package-lock.json generated
View File

@ -6,8 +6,8 @@
"dependencies": { "dependencies": {
"@ant-design-vue/use": { "@ant-design-vue/use": {
"version": "0.0.1-alpha.9", "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", "resolved": "https://registry.npmjs.org/@ant-design-vue/use/-/use-0.0.1-alpha.9.tgz",
"integrity": "sha1-t98mFQRpORODuhwt1LolAmelhBE=", "integrity": "sha512-X+ESJt+e95sRwlSkpzETjc0opE5l34tCjMEm92JkoM4BVl6YxG9IgyX1dBy0W2jF74SSCOiCc7GdIlsPFQvE/g==",
"requires": { "requires": {
"async-validator": "^3.4.0", "async-validator": "^3.4.0",
"lodash-es": "^4.17.15", "lodash-es": "^4.17.15",
@ -17,21 +17,21 @@
}, },
"@ant-design/colors": { "@ant-design/colors": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npm.taobao.org/@ant-design/colors/download/@ant-design/colors-5.1.1.tgz", "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-5.1.1.tgz",
"integrity": "sha1-gAshhrHifmZDLmfQPtlq8+IdiUA=", "integrity": "sha512-Txy4KpHrp3q4XZdfgOBqLl+lkQIc3tEvHXOimRN1giX1AEC7mGtyrO9p8iRGJ3FLuVMGa2gNEzQyghVymLttKQ==",
"requires": { "requires": {
"@ctrl/tinycolor": "^3.3.1" "@ctrl/tinycolor": "^3.3.1"
} }
}, },
"@ant-design/icons-svg": { "@ant-design/icons-svg": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npm.taobao.org/@ant-design/icons-svg/download/@ant-design/icons-svg-4.1.0.tgz", "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.1.0.tgz",
"integrity": "sha1-SAsCX0sg73/o9H1KSEbk/uhOoGw=" "integrity": "sha512-Fi03PfuUqRs76aI3UWYpP864lkrfPo0hluwGqh7NJdLhvH4iRDc3jbJqZIvRDLHKbXrvAfPPV3+zjUccfFvWOQ=="
}, },
"@ant-design/icons-vue": { "@ant-design/icons-vue": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npm.taobao.org/@ant-design/icons-vue/download/@ant-design/icons-vue-6.0.1.tgz", "resolved": "https://registry.npmjs.org/@ant-design/icons-vue/-/icons-vue-6.0.1.tgz",
"integrity": "sha1-nYBMPHTSz6+XyxjlgtO5QAk09f0=", "integrity": "sha512-HigIgEVV6bbcrz2A92/qDzi/aKWB5EC6b6E1mxMB6aQA7ksiKY+gi4U94TpqyEIIhR23uaDrjufJ+xCZQ+vx6Q==",
"requires": { "requires": {
"@ant-design/colors": "^5.0.0", "@ant-design/colors": "^5.0.0",
"@ant-design/icons-svg": "^4.0.0", "@ant-design/icons-svg": "^4.0.0",
@ -1412,8 +1412,8 @@
}, },
"@ctrl/tinycolor": { "@ctrl/tinycolor": {
"version": "3.4.0", "version": "3.4.0",
"resolved": "https://registry.npm.taobao.org/@ctrl/tinycolor/download/@ctrl/tinycolor-3.4.0.tgz", "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.4.0.tgz",
"integrity": "sha1-w8WuVDyJfKqcKmhjC+01W+X5mQ8=" "integrity": "sha512-JZButFdZ1+/xAfpguQHoabIXkcqRRKpMrWKBkpEZZyxfY9C1DpADFB8PEqGSTeFr135SaTRfKqGKx5xSCLI7ZQ=="
}, },
"@hapi/address": { "@hapi/address": {
"version": "2.1.4", "version": "2.1.4",
@ -1912,12 +1912,19 @@
} }
}, },
"@simonwep/pickr": { "@simonwep/pickr": {
"version": "1.8.0", "version": "1.8.1",
"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", "resolved": "https://registry.npmjs.org/@simonwep/pickr/-/pickr-1.8.1.tgz",
"integrity": "sha1-rb/5pPfw5Z3smUZQjF5IG3q64Pg=", "integrity": "sha512-3Q5+INWW0Py+/E9hgy0cyD0/0w/yGZbkxam6RzFVFDOEHgAqMVJR+x9znx58/ky/ZIvE/78FbH189yIC9h111A==",
"requires": { "requires": {
"core-js": "^3.8.0", "core-js": "^3.12.1",
"nanopop": "^2.1.0" "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": { "@soda/friendly-errors-webpack-plugin": {
@ -2153,12 +2160,6 @@
"integrity": "sha1-OkvSRRiw5sWUDaTiZZ7rLvCAaWM=", "integrity": "sha1-OkvSRRiw5sWUDaTiZZ7rLvCAaWM=",
"dev": true "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": { "@types/express": {
"version": "4.17.11", "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", "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 "dev": true
}, },
"@types/lodash": { "@types/lodash": {
"version": "4.14.168", "version": "4.14.169",
"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", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.169.tgz",
"integrity": "sha1-/iRjLnm3rePxMoka//hsql5c4Ag=" "integrity": "sha512-DvmZHoHTFJ8zhVYwCLWbQ7uAbYQEk52Ev2/ZiQ7Y7gQGeV9pjBqjnQpECMHfKS1rCYAhMI7LHVxwyZLZinJgdw=="
}, },
"@types/mdast": { "@types/mdast": {
"version": "3.0.3", "version": "3.0.3",
@ -2578,56 +2579,339 @@
"dev": true "dev": true
}, },
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
"version": "2.34.0", "version": "4.23.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", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.23.0.tgz",
"integrity": "sha1-b4zopGx96kpvHRcdK7j7rm2sK+k=", "integrity": "sha512-tGK1y3KIvdsQEEgq6xNn1DjiFJtl+wn8JJQiETtCbdQxw1vzjXyAaIkEmO2l6Nq24iy3uZBMFQjZ6ECf1QdgGw==",
"dev": true, "dev": true,
"requires": { "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", "functional-red-black-tree": "^1.0.1",
"lodash": "^4.17.15",
"regexpp": "^3.0.0", "regexpp": "^3.0.0",
"semver": "^7.3.2",
"tsutils": "^3.17.1" "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
}, },
"@typescript-eslint/experimental-utils": { "@typescript-eslint/experimental-utils": {
"version": "2.34.0", "version": "4.23.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", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.23.0.tgz",
"integrity": "sha1-01JLZEzbQO687KZ/jPPkzJyPmA8=", "integrity": "sha512-WAFNiTDnQfrF3Z2fQ05nmCgPsO5o790vOhmWKXbbYQTO9erE1/YsFot5/LnOUizLzU2eeuz6+U/81KV5/hFTGA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/json-schema": "^7.0.3", "@types/json-schema": "^7.0.3",
"@typescript-eslint/typescript-estree": "2.34.0", "@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-scope": "^5.0.0",
"eslint-utils": "^2.0.0" "eslint-utils": "^2.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=",
"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/typescript-estree": { "@typescript-eslint/typescript-estree": {
"version": "2.34.0", "version": "4.23.0",
"resolved": "https://registry.npm.taobao.org/@typescript-eslint/typescript-estree/download/@typescript-eslint/typescript-estree-2.34.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.23.0.tgz",
"integrity": "sha1-FK62NTs57wcyzH8bgoUpSTfPN9U=", "integrity": "sha512-5Sty6zPEVZF5fbvrZczfmLCOcby3sfrSPu30qKoY1U3mca5/jvU5cwsPb/CO6Q3ByRjixTMIVsDkqwIxCf/dMw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/types": "4.23.0",
"@typescript-eslint/visitor-keys": "4.23.0",
"debug": "^4.1.1", "debug": "^4.1.1",
"eslint-visitor-keys": "^1.1.0", "globby": "^11.0.1",
"glob": "^7.1.6",
"is-glob": "^4.0.1", "is-glob": "^4.0.1",
"lodash": "^4.17.15",
"semver": "^7.3.2", "semver": "^7.3.2",
"tsutils": "^3.17.1" "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": "4.23.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.23.0.tgz",
"integrity": "sha512-wsvjksHBMOqySy/Pi2Q6UuIuHYbgAMwLczRl4YanEPKW5KVxI9ZzDYh3B5DtcZPQTGRWFJrfcbJ6L01Leybwug==",
"dev": true,
"requires": {
"@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/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",
"globby": "^11.0.1",
"is-glob": "^4.0.1",
"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": { "@vue/babel-helper-vue-jsx-merge-props": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npm.taobao.org/@vue/babel-helper-vue-jsx-merge-props/download/@vue/babel-helper-vue-jsx-merge-props-1.2.1.tgz?cache=0&sync_timestamp=1602851135129&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40vue%2Fbabel-helper-vue-jsx-merge-props%2Fdownload%2F%40vue%2Fbabel-helper-vue-jsx-merge-props-1.2.1.tgz", "resolved": "https://registry.npm.taobao.org/@vue/babel-helper-vue-jsx-merge-props/download/@vue/babel-helper-vue-jsx-merge-props-1.2.1.tgz?cache=0&sync_timestamp=1602851135129&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40vue%2Fbabel-helper-vue-jsx-merge-props%2Fdownload%2F%40vue%2Fbabel-helper-vue-jsx-merge-props-1.2.1.tgz",
@ -3384,9 +3668,9 @@
} }
}, },
"@vue/eslint-config-typescript": { "@vue/eslint-config-typescript": {
"version": "5.1.0", "version": "7.0.0",
"resolved": "https://registry.npm.taobao.org/@vue/eslint-config-typescript/download/@vue/eslint-config-typescript-5.1.0.tgz", "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-7.0.0.tgz",
"integrity": "sha1-F+sa9k9j4jH8zspWA4Wb37T11OA=", "integrity": "sha512-UxUlvpSrFOoF8aQ+zX1leYiEBEm7CZmXYn/ZEM1zwSadUzpamx56RB4+Htdjisv1mX2tOjBegNUqH3kz2OL+Aw==",
"dev": true, "dev": true,
"requires": { "requires": {
"vue-eslint-parser": "^7.0.0" "vue-eslint-parser": "^7.0.0"
@ -3807,9 +4091,9 @@
} }
}, },
"ant-design-vue": { "ant-design-vue": {
"version": "2.1.2", "version": "2.1.6",
"resolved": "https://registry.npm.taobao.org/ant-design-vue/download/ant-design-vue-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-2.1.6.tgz",
"integrity": "sha1-IGXX5jGZwMWEkZRYr1e2oLWX9nc=", "integrity": "sha512-qICxb6Y4f7QuSuh/jbLhZA9SkUBnP9xYfy/E6yD7+1fg04aAzmRK8oLv8ETuGTrROVdSVeic9v/NS2BXEuuARg==",
"requires": { "requires": {
"@ant-design-vue/use": "^0.0.1-0", "@ant-design-vue/use": "^0.0.1-0",
"@ant-design/icons-vue": "^6.0.0", "@ant-design/icons-vue": "^6.0.0",
@ -3819,7 +4103,7 @@
"async-validator": "^3.3.0", "async-validator": "^3.3.0",
"dom-align": "^1.10.4", "dom-align": "^1.10.4",
"dom-scroll-into-view": "^2.0.0", "dom-scroll-into-view": "^2.0.0",
"is-mobile": "^2.2.1", "lodash": "^4.17.21",
"lodash-es": "^4.17.15", "lodash-es": "^4.17.15",
"moment": "^2.27.0", "moment": "^2.27.0",
"omit.js": "^2.0.0", "omit.js": "^2.0.0",
@ -3905,8 +4189,8 @@
}, },
"array-tree-filter": { "array-tree-filter": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npm.taobao.org/array-tree-filter/download/array-tree-filter-2.1.0.tgz", "resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz",
"integrity": "sha1-hzrAD+yDdJ8lWsjdCDgUtPYykZA=" "integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw=="
}, },
"array-union": { "array-union": {
"version": "1.0.2", "version": "1.0.2",
@ -4031,9 +4315,9 @@
"dev": true "dev": true
}, },
"async-validator": { "async-validator": {
"version": "3.5.1", "version": "3.5.2",
"resolved": "https://registry.npm.taobao.org/async-validator/download/async-validator-3.5.1.tgz", "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-3.5.2.tgz",
"integrity": "sha1-zWK5aIskZfSEIOJ620d2CrG1VZ8=" "integrity": "sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ=="
}, },
"asynckit": { "asynckit": {
"version": "0.4.0", "version": "0.4.0",
@ -5660,8 +5944,8 @@
}, },
"compute-scroll-into-view": { "compute-scroll-into-view": {
"version": "1.0.17", "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", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz",
"integrity": "sha1-aojxis2dQunPS6pr7H4FImB6t6s=" "integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg=="
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
@ -6929,9 +7213,9 @@
} }
}, },
"dom-align": { "dom-align": {
"version": "1.12.0", "version": "1.12.1",
"resolved": "https://registry.npm.taobao.org/dom-align/download/dom-align-1.12.0.tgz", "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.1.tgz",
"integrity": "sha1-VvtxVt8LkQmYMDZNLUj4iWP1opw=" "integrity": "sha512-CdTD9EdA5WviP8oO3n+okOm0Xt7dSuWxRTLcJiW0memwUr3Tvz66JDDCh9cb50IZFHXvBmLoyX454uJU/EVg+g=="
}, },
"dom-converter": { "dom-converter": {
"version": "0.2.0", "version": "0.2.0",
@ -6944,8 +7228,8 @@
}, },
"dom-scroll-into-view": { "dom-scroll-into-view": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npm.taobao.org/dom-scroll-into-view/download/dom-scroll-into-view-2.0.1.tgz", "resolved": "https://registry.npmjs.org/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz",
"integrity": "sha1-DezIUigB/Y0/HGujVadNOCxfmJs=" "integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w=="
}, },
"dom-serializer": { "dom-serializer": {
"version": "0.2.2", "version": "0.2.2",
@ -9116,11 +9400,6 @@
"integrity": "sha1-e15vfmZen7QfMAB+2eDUHpf7IUA=", "integrity": "sha1-e15vfmZen7QfMAB+2eDUHpf7IUA=",
"dev": true "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": { "html-webpack-plugin": {
"version": "3.2.0", "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", "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=", "integrity": "sha1-zDXJdYjaS9Saju3WvECC1E3LI6c=",
"dev": true "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": { "is-negative-zero": {
"version": "2.0.1", "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", "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": { "is-plain-object": {
"version": "3.0.1", "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", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz",
"integrity": "sha1-Zi2S0kwKpDAkB7DUXSHyJRyF+Fs=" "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g=="
}, },
"is-regex": { "is-regex": {
"version": "1.1.2", "version": "1.1.2",
@ -12465,8 +12739,8 @@
}, },
"lodash-es": { "lodash-es": {
"version": "4.17.21", "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", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha1-Q+YmxG5lkbd1C+srUBFzkMYJ4+4=" "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
}, },
"lodash.camelcase": { "lodash.camelcase": {
"version": "4.3.0", "version": "4.3.0",
@ -13151,8 +13425,8 @@
}, },
"moment": { "moment": {
"version": "2.29.1", "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", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha1-sr52n6MZQL6e7qZGnAdeNQBvo9M=" "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
}, },
"move-concurrently": { "move-concurrently": {
"version": "1.0.1", "version": "1.0.1",
@ -13241,8 +13515,8 @@
}, },
"nanopop": { "nanopop": {
"version": "2.1.0", "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", "resolved": "https://registry.npmjs.org/nanopop/-/nanopop-2.1.0.tgz",
"integrity": "sha1-I0dlE87iQFiIr9LopLVAZrcLnmA=" "integrity": "sha512-jGTwpFRexSH+fxappnGQtN9dspgE2ipa1aOjtR24igG0pv6JCxImIAmrLRHX+zUF5+1wtsFVbKyfP51kIGAVNw=="
}, },
"native-request": { "native-request": {
"version": "1.0.8", "version": "1.0.8",
@ -13653,8 +13927,8 @@
}, },
"omit.js": { "omit.js": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npm.taobao.org/omit.js/download/omit.js-2.0.2.tgz", "resolved": "https://registry.npmjs.org/omit.js/-/omit.js-2.0.2.tgz",
"integrity": "sha1-3ZuENvq5R6Xz/yFMslOGMeMT7C8=" "integrity": "sha512-hJmu9D+bNB40YpL9jYebQl4lsTW6yEHRTroJzNLqQJYHm7c+NQnJGfZmIWh8S3q3KoaxV1aLhV6B3+0N0/kyJg=="
}, },
"on-finished": { "on-finished": {
"version": "2.3.0", "version": "2.3.0",
@ -15591,8 +15865,8 @@
}, },
"regexpp": { "regexpp": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npm.taobao.org/regexpp/download/regexpp-3.1.0.tgz", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
"integrity": "sha1-IG0K0KVkjP+9uK5GQ489xRyfeOI=", "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
"dev": true "dev": true
}, },
"regexpu-core": { "regexpu-core": {
@ -15789,8 +16063,8 @@
}, },
"resize-observer-polyfill": { "resize-observer-polyfill": {
"version": "1.5.1", "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", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
"integrity": "sha1-DpAg3T0hAkRY1OvSfiPkAmmBBGQ=" "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
}, },
"resolve": { "resolve": {
"version": "1.20.0", "version": "1.20.0",
@ -16003,12 +16277,12 @@
} }
}, },
"sass": { "sass": {
"version": "1.32.8", "version": "1.32.13",
"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", "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.13.tgz",
"integrity": "sha1-8WqavY3FMK3Yg05QaHiigIwDe9w=", "integrity": "sha512-dEgI9nShraqP7cXQH+lEXVf73WOPCse0QlFzSD8k+1TcOxCMwVXfQlr0jtoluZysQOyJGnfr21dLvYKDJq8HkA==",
"dev": true, "dev": true,
"requires": { "requires": {
"chokidar": ">=2.0.0 <4.0.0" "chokidar": ">=3.0.0 <4.0.0"
} }
}, },
"sass-loader": { "sass-loader": {
@ -16060,8 +16334,8 @@
}, },
"scroll-into-view-if-needed": { "scroll-into-view-if-needed": {
"version": "2.2.28", "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", "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.28.tgz",
"integrity": "sha1-WhWy9YpSZCyIyOylhGROAXA9ZFo=", "integrity": "sha512-8LuxJSuFVc92+0AdNv4QOxRL4Abeo1DgLnGNkn1XlaujPH/3cCFz3QI60r2VNu4obJJROzgnIUw5TKQkZvZI1w==",
"requires": { "requires": {
"compute-scroll-into-view": "^1.0.17" "compute-scroll-into-view": "^1.0.17"
} }
@ -16304,8 +16578,8 @@
}, },
"shallow-equal": { "shallow-equal": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npm.taobao.org/shallow-equal/download/shallow-equal-1.2.1.tgz", "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz",
"integrity": "sha1-TBar+lYEOqINBQMk76aJQLDaedo=" "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA=="
}, },
"shebang-command": { "shebang-command": {
"version": "1.2.0", "version": "1.2.0",
@ -18392,9 +18666,9 @@
} }
}, },
"typescript": { "typescript": {
"version": "3.9.9", "version": "4.2.4",
"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", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
"integrity": "sha1-5pkFxUvAaB0FGL1NWHzG8tCxpnQ=", "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==",
"dev": true "dev": true
}, },
"uglify-js": { "uglify-js": {
@ -19004,8 +19278,8 @@
}, },
"vue-types": { "vue-types": {
"version": "3.0.2", "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", "resolved": "https://registry.npmjs.org/vue-types/-/vue-types-3.0.2.tgz",
"integrity": "sha1-7BbgXUEsA4Ji/B76TOuWR+f7YB0=", "integrity": "sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==",
"requires": { "requires": {
"is-plain-object": "3.0.1" "is-plain-object": "3.0.1"
} }
@ -19059,8 +19333,8 @@
}, },
"warning": { "warning": {
"version": "4.0.3", "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", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
"integrity": "sha1-Fungd+uKhtavfWSqHgX9hbRnjKM=", "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
"requires": { "requires": {
"loose-envify": "^1.0.0" "loose-envify": "^1.0.0"
} }

View File

@ -11,14 +11,13 @@
"dependencies": { "dependencies": {
"@icon-park/vue-next": "^1.2.6", "@icon-park/vue-next": "^1.2.6",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"ant-design-vue": "^2.1.2", "ant-design-vue": "^2.1.6",
"chartist": "^0.11.4", "chartist": "^0.11.4",
"clipboard": "^2.0.6", "clipboard": "^2.0.6",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",
"dexie": "^3.0.3", "dexie": "^3.0.3",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"html-to-image": "^1.6.0",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"mitt": "^2.1.0", "mitt": "^2.1.0",
"prosemirror-commands": "^1.1.7", "prosemirror-commands": "^1.1.7",
@ -54,8 +53,8 @@
"@types/prosemirror-schema-list": "^1.0.1", "@types/prosemirror-schema-list": "^1.0.1",
"@types/resize-observer-browser": "^0.1.4", "@types/resize-observer-browser": "^0.1.4",
"@types/tinycolor2": "^1.4.2", "@types/tinycolor2": "^1.4.2",
"@typescript-eslint/eslint-plugin": "^2.33.0", "@typescript-eslint/eslint-plugin": "^4.23.0",
"@typescript-eslint/parser": "^2.33.0", "@typescript-eslint/parser": "^4.23.0",
"@vue/cli-plugin-babel": "~4.5.0", "@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0", "@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0", "@vue/cli-plugin-typescript": "~4.5.0",
@ -63,7 +62,7 @@
"@vue/cli-plugin-vuex": "~4.5.0", "@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0", "@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.11", "@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", "@vue/test-utils": "^2.0.0-0",
"babel-plugin-import": "^1.13.3", "babel-plugin-import": "^1.13.3",
"eslint": "^6.7.2", "eslint": "^6.7.2",
@ -71,12 +70,12 @@
"husky": "^4.3.8", "husky": "^4.3.8",
"less": "^3.12.2", "less": "^3.12.2",
"less-loader": "^7.1.0", "less-loader": "^7.1.0",
"sass": "^1.26.5", "sass": "^1.32.13",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"stylelint": "^13.8.0", "stylelint": "^13.8.0",
"stylelint-config-standard": "^20.0.0", "stylelint-config-standard": "^20.0.0",
"stylelint-webpack-plugin": "^2.1.1", "stylelint-webpack-plugin": "^2.1.1",
"typescript": "~3.9.3", "typescript": "^4.2.4",
"vue-jest": "^5.0.0-0" "vue-jest": "^5.0.0-0"
}, },
"husky": { "husky": {

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -19,8 +19,8 @@
src: url(../fonts/站酷快乐体.ttf); src: url(../fonts/站酷快乐体.ttf);
} }
@font-face { @font-face {
font-family: '站酷酷黑'; font-family: '字制区喜脉';
src: url(../fonts/站酷酷黑体.ttf); src: url(../fonts/字制区喜脉体.ttf);
} }
@font-face { @font-face {
font-family: '素材集市康康体'; font-family: '素材集市康康体';
@ -31,8 +31,8 @@
src: url(../fonts/联盟起艺卢帅正锐黑体.ttf); src: url(../fonts/联盟起艺卢帅正锐黑体.ttf);
} }
@font-face { @font-face {
font-family: '谦度手写楷'; font-family: '素材集市酷方';
src: url(../fonts/谦度手写楷体.ttf); src: url(../fonts/素材集市酷方体.ttf);
} }
@font-face { @font-face {
font-family: '途牛类圆体'; font-family: '途牛类圆体';
@ -43,6 +43,6 @@
src: url(../fonts/锐字真言体.ttf); src: url(../fonts/锐字真言体.ttf);
} }
@font-face { @font-face {
font-family: '问藏书房'; font-family: '阿里汉仪智能黑体';
src: url(../fonts/问藏书房.ttf); src: url(../fonts/阿里汉仪智能黑体.ttf);
} }

View File

@ -37,7 +37,7 @@ export default defineComponent({
const color = computed(() => { const color = computed(() => {
const hsla = tinycolor(props.value).toHsl() const hsla = tinycolor(props.value).toHsl()
if (hsla.s === 0) hsla.h = props.hue if (props.hue !== -1) hsla.h = props.hue
return hsla return hsla
}) })
@ -68,9 +68,9 @@ export default defineComponent({
else if (left > containerWidth) h = 360 else if (left > containerWidth) h = 360
else { else {
percent = left * 100 / containerWidth 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', { emit('colorChange', {
h, h,
l: color.value.l, l: color.value.l,

View File

@ -39,7 +39,7 @@ export default defineComponent({
setup(props, { emit }) { setup(props, { emit }) {
const color = computed(() => { const color = computed(() => {
const hsva = tinycolor(props.value).toHsv() const hsva = tinycolor(props.value).toHsv()
if (hsva.s === 0) hsva.h = props.hue if (props.hue !== -1) hsva.h = props.hue
return hsva return hsva
}) })

View File

@ -140,7 +140,7 @@ export default defineComponent({
}, },
}, },
setup(props, { emit }) { setup(props, { emit }) {
const hue = ref(0) const hue = ref(-1)
const recentColors = ref<string[]>([]) const recentColors = ref<string[]>([])
const color = computed({ const color = computed({
@ -162,6 +162,7 @@ export default defineComponent({
}) })
const selectPresetColor = (colorString: string) => { const selectPresetColor = (colorString: string) => {
hue.value = tinycolor(colorString).toHsl().h
emit('update:modelValue', colorString) emit('update:modelValue', colorString)
} }
@ -193,7 +194,10 @@ export default defineComponent({
hue.value = value.h hue.value = value.h
color.value = tinycolor(value).toRgb() color.value = tinycolor(value).toRgb()
} }
else color.value = value else {
hue.value = tinycolor(value).toHsl().h
color.value = value
}
updateRecentColorsCache() updateRecentColorsCache()
} }

View File

@ -4,6 +4,9 @@
@mousedown="$event => handleMousedown($event)" @mousedown="$event => handleMousedown($event)"
@mousemove="$event => handleMousemove($event)" @mousemove="$event => handleMousemove($event)"
@mouseup="handleMouseup()" @mouseup="handleMouseup()"
@touchstart="$event => handleMousedown($event)"
@touchmove="$event => handleMousemove($event)"
@touchend="handleMouseup(); mouseInCanvas = false"
@mouseleave="handleMouseup(); mouseInCanvas = false" @mouseleave="handleMouseup(); mouseInCanvas = false"
@mouseenter="mouseInCanvas = true" @mouseenter="mouseInCanvas = true"
></canvas> ></canvas>
@ -67,10 +70,11 @@ export default defineComponent({
x: 0, x: 0,
y: 0, y: 0,
}) })
// //
const updateMousePosition = (e: MouseEvent) => { const updateMousePosition = (x: number, y: number) => {
mouse.x = e.pageX mouse.x = x
mouse.y = e.pageY mouse.y = y
} }
// //
@ -145,13 +149,6 @@ export default defineComponent({
ctx.restore() 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 getDistance = (posX: number, posY: number) => {
const lastPosX = lastPos.x const lastPosX = lastPos.x
@ -176,28 +173,50 @@ export default defineComponent({
return lineWidth * 1 / 3 + lastLineWidth * 2 / 3 return lineWidth * 1 / 3 + lastLineWidth * 2 / 3
} }
// / //
const handleMousemove = (e: MouseEvent) => { const handleMove = (x: number, y: number) => {
updateMousePosition(e)
if (!isMouseDown) return
const time = new Date().getTime() const time = new Date().getTime()
if (props.model === 'pen') { if (props.model === 'pen') {
const s = getDistance(e.offsetX, e.offsetY) const s = getDistance(x, y)
const t = time - lastTime const t = time - lastTime
const lineWidth = getLineWidth(s, t) const lineWidth = getLineWidth(s, t)
draw(e.offsetX, e.offsetY, lineWidth) draw(x, y, lineWidth)
lastLineWidth = lineWidth lastLineWidth = lineWidth
} }
else erase(e.offsetX, e.offsetY) else erase(x, y)
lastPos = { x: e.offsetX, y: e.offsetY } lastPos = {x, y}
lastTime = new Date().getTime() 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 = () => { const handleMouseup = () => {
if (!isMouseDown) return if (!isMouseDown) return

View File

@ -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: '谦度手写楷体' }, { label: '素材集市酷方体', value: '素材集市酷方体' },
{ label: '途牛类圆体', value: '途牛类圆体' }, { label: '途牛类圆体', value: '途牛类圆体' },
{ label: '锐字真言体', value: '锐字真言体' }, { label: '锐字真言体', value: '锐字真言体' },
{ label: '问藏书房', value: '问藏书房' }, { label: '阿里汉仪智能黑体', value: '阿里汉仪智能黑体' },
] ]

View File

@ -55,8 +55,8 @@ export const HOTKEY_DOC = [
{ label: '放大画布', value: 'Ctrl + =' }, { label: '放大画布', value: 'Ctrl + =' },
{ label: '缩小画布', value: 'Ctrl + -' }, { label: '缩小画布', value: 'Ctrl + -' },
{ label: '缩放画布到合适大小', value: 'Ctrl + 0' }, { 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: 'Alt + B' },
{ label: '锁定宽高比例', value: '按住 Ctrl 或 Shift' }, { label: '锁定宽高比例', value: '按住 Ctrl 或 Shift' },
{ label: '创建水平 / 垂直线条', value: '按住 Ctrl 或 Shift' }, { label: '创建水平 / 垂直线条', value: '按住 Ctrl 或 Shift' },
{ label: '切换焦点元素', value: 'Tab' },
{ label: '确认图片裁剪', value: 'Enter' }, { label: '确认图片裁剪', value: 'Enter' },
], ],
}, },

View File

@ -63,6 +63,7 @@ export default () => {
left: (VIEWPORT_SIZE - width) / 2, left: (VIEWPORT_SIZE - width) / 2,
top: (VIEWPORT_SIZE * viewportRatio.value - height) / 2, top: (VIEWPORT_SIZE * viewportRatio.value - height) / 2,
fixedRatio: true, fixedRatio: true,
rotate: 0,
}) })
}) })
} }
@ -147,6 +148,7 @@ export default () => {
width, width,
height, height,
content, content,
rotate: 0,
}) })
} }
@ -168,6 +170,7 @@ export default () => {
path: data.path, path: data.path,
fill: themeColor.value, fill: themeColor.value,
fixedRatio: false, fixedRatio: false,
rotate: 0,
}) })
} }

View File

@ -1,19 +1,29 @@
import { computed } from 'vue' import { computed } from 'vue'
import { MutationTypes, useStore } from '@/store' import { MutationTypes, useStore } from '@/store'
import { Slide } from '@/types/slides' import { PPTElement, Slide } from '@/types/slides'
import useHistorySnapshot from '@/hooks/useHistorySnapshot' import useHistorySnapshot from '@/hooks/useHistorySnapshot'
export default () => { export default () => {
const store = useStore() const store = useStore()
const activeElementIdList = computed(() => store.state.activeElementIdList) const activeElementIdList = computed(() => store.state.activeElementIdList)
const activeGroupElementId = computed(() => store.state.activeGroupElementId)
const currentSlide = computed<Slide>(() => store.getters.currentSlide) const currentSlide = computed<Slide>(() => store.getters.currentSlide)
const { addHistorySnapshot } = useHistorySnapshot() const { addHistorySnapshot } = useHistorySnapshot()
// 删除全部选中元素 // 删除全部选中元素
// 组合元素成员中,存在被选中可独立操作的元素时,优先删除该元素。否则默认删除所有被选中的元素
const deleteElement = () => { const deleteElement = () => {
if (!activeElementIdList.value.length) return 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.SET_ACTIVE_ELEMENT_ID_LIST, [])
store.commit(MutationTypes.UPDATE_SLIDE, { elements: newElementList }) store.commit(MutationTypes.UPDATE_SLIDE, { elements: newElementList })
addHistorySnapshot() addHistorySnapshot()

View File

@ -1,7 +1,7 @@
import { computed, onMounted, onUnmounted } from 'vue' import { computed, onMounted, onUnmounted } from 'vue'
import { MutationTypes, useStore } from '@/store' import { MutationTypes, useStore } from '@/store'
import { ElementOrderCommand, ElementOrderCommands } from '@/types/edit' import { ElementOrderCommand, ElementOrderCommands } from '@/types/edit'
import { PPTElement } from '@/types/slides' import { PPTElement, Slide } from '@/types/slides'
import { KEYS } from '@/configs/hotkey' import { KEYS } from '@/configs/hotkey'
import useSlideHandler from './useSlideHandler' import useSlideHandler from './useSlideHandler'
@ -24,6 +24,7 @@ export default () => {
const disableHotkeys = computed(() => store.state.disableHotkeys) const disableHotkeys = computed(() => store.state.disableHotkeys)
const activeElementIdList = computed(() => store.state.activeElementIdList) const activeElementIdList = computed(() => store.state.activeElementIdList)
const handleElement = computed<PPTElement>(() => store.getters.handleElement) const handleElement = computed<PPTElement>(() => store.getters.handleElement)
const currentSlide = computed<Slide>(() => store.getters.currentSlide)
const editorAreaFocus = computed(() => store.state.editorAreaFocus) const editorAreaFocus = computed(() => store.state.editorAreaFocus)
const thumbnailsFocus = computed(() => store.state.thumbnailsFocus) const thumbnailsFocus = computed(() => store.state.thumbnailsFocus)
@ -103,15 +104,31 @@ export default () => {
createSlide() 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 keydownListener = (e: KeyboardEvent) => {
const { ctrlKey, shiftKey, altKey, metaKey } = e const { ctrlKey, shiftKey, altKey, metaKey } = e
const ctrlOrMetaKeyActive = ctrlKey || metaKey
const key = e.key.toUpperCase() 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 (shiftKey && !shiftKeyActive.value) store.commit(MutationTypes.SET_SHIFT_KEY_STATE, true)
if (ctrlKey && key === KEYS.F) { if (ctrlOrMetaKeyActive && key === KEYS.F) {
e.preventDefault() e.preventDefault()
enterScreening() enterScreening()
store.commit(MutationTypes.SET_CTRL_KEY_STATE, false) store.commit(MutationTypes.SET_CTRL_KEY_STATE, false)
@ -119,47 +136,47 @@ export default () => {
if (!editorAreaFocus.value && !thumbnailsFocus.value) return if (!editorAreaFocus.value && !thumbnailsFocus.value) return
if ((ctrlKey || metaKey) && key === KEYS.C) { if (ctrlOrMetaKeyActive && key === KEYS.C) {
if (disableHotkeys.value) return if (disableHotkeys.value) return
e.preventDefault() e.preventDefault()
copy() copy()
} }
if (ctrlKey && key === KEYS.X) { if (ctrlOrMetaKeyActive && key === KEYS.X) {
if (disableHotkeys.value) return if (disableHotkeys.value) return
e.preventDefault() e.preventDefault()
cut() cut()
} }
if (ctrlKey && key === KEYS.D) { if (ctrlOrMetaKeyActive && key === KEYS.D) {
if (disableHotkeys.value) return if (disableHotkeys.value) return
e.preventDefault() e.preventDefault()
quickCopy() quickCopy()
} }
if (ctrlKey && key === KEYS.Z) { if (ctrlOrMetaKeyActive && key === KEYS.Z) {
if (disableHotkeys.value) return if (disableHotkeys.value) return
e.preventDefault() e.preventDefault()
undo() undo()
} }
if (ctrlKey && key === KEYS.Y) { if (ctrlOrMetaKeyActive && key === KEYS.Y) {
if (disableHotkeys.value) return if (disableHotkeys.value) return
e.preventDefault() e.preventDefault()
redo() redo()
} }
if (ctrlKey && key === KEYS.A) { if (ctrlOrMetaKeyActive && key === KEYS.A) {
if (disableHotkeys.value) return if (disableHotkeys.value) return
e.preventDefault() e.preventDefault()
selectAll() selectAll()
} }
if (ctrlKey && key === KEYS.L) { if (ctrlOrMetaKeyActive && key === KEYS.L) {
if (disableHotkeys.value) return if (disableHotkeys.value) return
e.preventDefault() e.preventDefault()
lock() lock()
} }
if (!shiftKey && ctrlKey && key === KEYS.G) { if (!shiftKey && ctrlOrMetaKeyActive && key === KEYS.G) {
if (disableHotkeys.value) return if (disableHotkeys.value) return
e.preventDefault() e.preventDefault()
combine() combine()
} }
if (shiftKey && ctrlKey && key === KEYS.G) { if (shiftKey && ctrlOrMetaKeyActive && key === KEYS.G) {
if (disableHotkeys.value) return if (disableHotkeys.value) return
e.preventDefault() e.preventDefault()
uncombine() uncombine()
@ -219,6 +236,11 @@ export default () => {
e.preventDefault() e.preventDefault()
setCanvasPercentage(90) setCanvasPercentage(90)
} }
if (key === KEYS.TAB) {
if (disableHotkeys.value) return
e.preventDefault()
tabActiveElement()
}
} }
const keyupListener = () => { const keyupListener = () => {

View File

@ -43,14 +43,12 @@ export default () => {
* @param command * @param command
*/ */
const updateSlideIndex = (command: string) => { const updateSlideIndex = (command: string) => {
let targetIndex = 0
if (command === KEYS.UP && slideIndex.value > 0) { if (command === KEYS.UP && slideIndex.value > 0) {
targetIndex = slideIndex.value - 1 store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value - 1)
} }
else if (command === KEYS.DOWN && slideIndex.value < slides.value.length - 1) { else if (command === KEYS.DOWN && slideIndex.value < slides.value.length - 1) {
targetIndex = slideIndex.value + 1 store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value + 1)
} }
store.commit(MutationTypes.UPDATE_SLIDE_INDEX, targetIndex)
} }
// 将当前页面数据加密后复制到剪贴板 // 将当前页面数据加密后复制到剪贴板
@ -82,6 +80,7 @@ export default () => {
color: theme.value.backgroundColor, color: theme.value.backgroundColor,
}, },
} }
store.commit(MutationTypes.SET_ACTIVE_ELEMENT_ID_LIST, [])
store.commit(MutationTypes.ADD_SLIDE, emptySlide) store.commit(MutationTypes.ADD_SLIDE, emptySlide)
addHistorySnapshot() addHistorySnapshot()
} }

View File

@ -23,19 +23,24 @@ export default () => {
const copyOfActiveElementList: PPTElement[] = JSON.parse(JSON.stringify(activeElementList.value)) const copyOfActiveElementList: PPTElement[] = JSON.parse(JSON.stringify(activeElementList.value))
const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements)) const newElementList: PPTElement[] = JSON.parse(JSON.stringify(currentSlide.value.elements))
// 将选中的元素按位置(从左到右)排序
copyOfActiveElementList.sort((elementA, elementB) => { copyOfActiveElementList.sort((elementA, elementB) => {
const { minX: elAMinX } = getElementRange(elementA) const { minX: elAMinX } = getElementRange(elementA)
const { minX: elBMinX } = getElementRange(elementB) const { minX: elBMinX } = getElementRange(elementB)
return elAMinX - elBMinX return elAMinX - elBMinX
}) })
let totaiWidth = 0 // 计算元素均匀分布所需要的间隔:
// (所选元素整体范围 - 所有所选元素宽度和) / (所选元素数 - 1)
let totalWidth = 0
for (const element of activeElementList.value) { for (const element of activeElementList.value) {
const { minX: elMinX, maxX: elMaxX } = getElementRange(element) 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[] = [] const sortedElementData: SortedElementData[] = []
for (const element of copyOfActiveElementList) { for (const element of copyOfActiveElementList) {
if (!sortedElementData.length) { if (!sortedElementData.length) {
@ -52,6 +57,8 @@ export default () => {
sortedElementData.push({ el: element, pos: lastItemPos + lastElementWidth + span }) sortedElementData.push({ el: element, pos: lastItemPos + lastElementWidth + span })
} }
// 根据目标位置计算元素最终目标left值
// 对于旋转后的元素需要计算旋转前后left的偏移来做校正
for (const element of newElementList) { for (const element of newElementList) {
if (!activeElementIdList.value.includes(element.id)) continue if (!activeElementIdList.value.includes(element.id)) continue
@ -76,7 +83,7 @@ export default () => {
addHistorySnapshot() addHistorySnapshot()
} }
// 垂直均匀排列 // 垂直均匀排列(逻辑类似水平均匀排列方法)
const uniformVerticalDisplay = () => { const uniformVerticalDisplay = () => {
const { minY, maxY } = getElementListRange(activeElementList.value) const { minY, maxY } = getElementListRange(activeElementList.value)
const copyOfActiveElementList: PPTElement[] = JSON.parse(JSON.stringify(activeElementList.value)) const copyOfActiveElementList: PPTElement[] = JSON.parse(JSON.stringify(activeElementList.value))
@ -88,12 +95,12 @@ export default () => {
return elAMinY - elBMinY return elAMinY - elBMinY
}) })
let totaiHeight = 0 let totalHeight = 0
for (const element of activeElementList.value) { for (const element of activeElementList.value) {
const { minY: elMinY, maxY: elMaxY } = getElementRange(element) 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[] = [] const sortedElementData: SortedElementData[] = []
for (const element of copyOfActiveElementList) { for (const element of copyOfActiveElementList) {

View File

@ -13,9 +13,10 @@ export const slides: Slide[] = [
height: 362.5, height: 362.5,
viewBox: 200, viewBox: 200,
path: 'M 0 0 L 0 200 L 200 200 Z', path: 'M 0 0 L 0 200 L 200 200 Z',
fill: '#d14424', fill: '#5b9bd5',
fixedRatio: false, fixedRatio: false,
opacity: 0.7, opacity: 0.7,
rotate: 0
}, },
{ {
type: 'shape', type: 'shape',
@ -26,12 +27,13 @@ export const slides: Slide[] = [
height: 320, height: 320,
viewBox: 200, viewBox: 200,
path: 'M 0 0 L 0 200 L 200 200 Z', path: 'M 0 0 L 0 200 L 200 200 Z',
fill: '#d14424', fill: '#5b9bd5',
fixedRatio: false, fixedRatio: false,
flip: { flip: {
x: 180, x: 180,
y: 0, y: 0,
}, },
rotate: 0
}, },
{ {
type: 'text', type: 'text',
@ -40,7 +42,9 @@ export const slides: Slide[] = [
top: 65.25, top: 65.25,
width: 585, width: 585,
height: 188, height: 188,
lineHeight: 1.2,
content: '<p style=\'\'><strong><span style=\'font-size: 112px\'>PPTIST</span></strong></p>', content: '<p style=\'\'><strong><span style=\'font-size: 112px\'>PPTIST</span></strong></p>',
rotate: 0
}, },
{ {
type: 'text', type: 'text',
@ -50,6 +54,7 @@ export const slides: Slide[] = [
width: 585, width: 585,
height: 56, height: 56,
content: '<p style=\'\'><span style=\'font-size: 24px\'>基于 Vue 3.x + TypeScript 的在线演示文稿应用</span></p>', content: '<p style=\'\'><span style=\'font-size: 24px\'>基于 Vue 3.x + TypeScript 的在线演示文稿应用</span></p>',
rotate: 0
}, },
{ {
type: 'line', type: 'line',
@ -59,7 +64,7 @@ export const slides: Slide[] = [
start: [0, 0], start: [0, 0],
end: [549, 0], end: [549, 0],
points: ['', ''], points: ['', ''],
color: '#d14424', color: '#5b9bd5',
style: 'solid', style: 'solid',
width: 2, width: 2,
}, },

8
src/mocks/theme.ts Normal file
View File

@ -0,0 +1,8 @@
import { SlideTheme } from '@/types/slides'
export const theme: SlideTheme = {
themeColor: '#5b9bd5',
fontColor: '#333',
fontName: '微软雅黑',
backgroundColor: '#fff',
}

View File

@ -69,6 +69,7 @@ import {
Platte, Platte,
UpOne, UpOne,
DownOne, DownOne,
Close,
CloseSmall, CloseSmall,
Undo, Undo,
Transform, Transform,
@ -76,6 +77,9 @@ import {
Theme, Theme,
ArrowCircleLeft, ArrowCircleLeft,
GraphicDesign, GraphicDesign,
Logout,
Erase,
Clear,
} from '@icon-park/vue-next' } from '@icon-park/vue-next'
export default { export default {
@ -154,6 +158,7 @@ export default {
app.component('IconRightTwo', RightTwo) app.component('IconRightTwo', RightTwo)
app.component('IconPlus', Plus) app.component('IconPlus', Plus)
app.component('IconMinus', Minus) app.component('IconMinus', Minus)
app.component('IconClose', Close)
app.component('IconCloseSmall', CloseSmall) app.component('IconCloseSmall', CloseSmall)
// 图表 // 图表
@ -171,6 +176,7 @@ export default {
app.component('IconHelpcenter', Helpcenter) app.component('IconHelpcenter', Helpcenter)
app.component('IconGithub', Github) app.component('IconGithub', Github)
app.component('IconWrite', Write) app.component('IconWrite', Write)
app.component('IconErase', Erase)
app.component('IconEffects', Effects) app.component('IconEffects', Effects)
app.component('IconRotate', Rotate) app.component('IconRotate', Rotate)
app.component('IconEdit', Edit) app.component('IconEdit', Edit)
@ -179,5 +185,7 @@ export default {
app.component('IconClick', Click) app.component('IconClick', Click)
app.component('IconTheme', Theme) app.component('IconTheme', Theme)
app.component('IconArrowCircleLeft', ArrowCircleLeft) app.component('IconArrowCircleLeft', ArrowCircleLeft)
app.component('IconLogout', Logout)
app.component('IconClear', Clear)
} }
} }

View File

@ -23,26 +23,38 @@ export const actions: ActionTree<State, State> = {
}, },
async [ActionTypes.ADD_SNAPSHOT]({ state, commit }) { async [ActionTypes.ADD_SNAPSHOT]({ state, commit }) {
// 获取当前indexeddb中全部快照的ID
const allKeys = await snapshotDB.snapshots.orderBy('id').keys() const allKeys = await snapshotDB.snapshots.orderBy('id').keys()
let needDeleteKeys: IndexableTypeArray = [] let needDeleteKeys: IndexableTypeArray = []
// 记录需要删除的快照ID
// 若当前快照指针不处在最后一位,那么再添加快照时,应该将当前指针位置后面的快照全部删除,对应的实际情况是:
// 用户撤回多次后,再进行操作(添加快照),此时原先被撤销的快照都应该被删除
if (state.snapshotCursor >= 0 && state.snapshotCursor < allKeys.length - 1) { if (state.snapshotCursor >= 0 && state.snapshotCursor < allKeys.length - 1) {
needDeleteKeys = allKeys.slice(state.snapshotCursor + 1) needDeleteKeys = allKeys.slice(state.snapshotCursor + 1)
} }
// 添加新快照
const snapshot = { const snapshot = {
index: state.slideIndex, index: state.slideIndex,
slides: state.slides, slides: state.slides,
} }
await snapshotDB.snapshots.add(snapshot) await snapshotDB.snapshots.add(snapshot)
// 计算当前快照长度,用于设置快照指针的位置(此时指针应该处在最后一位,即:快照长度 - 1
let snapshotLength = allKeys.length - needDeleteKeys.length + 1 let snapshotLength = allKeys.length - needDeleteKeys.length + 1
if (snapshotLength > 20) { // 快照数量超过长度限制时,应该将头部多余的快照删除
const snapshotLengthLimit = 20
if (snapshotLength > snapshotLengthLimit) {
needDeleteKeys.push(allKeys[0]) needDeleteKeys.push(allKeys[0])
snapshotLength-- snapshotLength--
} }
// 快照数大于1时需要保证撤回操作后维持页面焦点不变也就是将倒数第二个快照对应的索引设置为当前页的索引
// https://github.com/pipipi-pikachu/PPTist/issues/27
if (snapshotLength >= 2) { if (snapshotLength >= 2) {
snapshotDB.snapshots.update(allKeys[snapshotLength - 2] as number, { index: state.slideIndex }) snapshotDB.snapshots.update(allKeys[snapshotLength - 2] as number, { index: state.slideIndex })
} }

View File

@ -3,6 +3,7 @@ export const enum MutationTypes {
// editor // editor
SET_ACTIVE_ELEMENT_ID_LIST = 'setActiveElementIdList', SET_ACTIVE_ELEMENT_ID_LIST = 'setActiveElementIdList',
SET_HANDLE_ELEMENT_ID = 'setHandleElementId', SET_HANDLE_ELEMENT_ID = 'setHandleElementId',
SET_ACTIVE_GROUP_ELEMENT_ID = 'setActiveGroupElementId',
SET_CANVAS_PERCENTAGE = 'setCanvasPercentage', SET_CANVAS_PERCENTAGE = 'setCanvasPercentage',
SET_CANVAS_SCALE = 'setCanvasScale', SET_CANVAS_SCALE = 'setCanvasScale',
SET_THUMBNAILS_FOCUS = 'setThumbnailsFocus', SET_THUMBNAILS_FOCUS = 'setThumbnailsFocus',

View File

@ -32,6 +32,10 @@ export const mutations: MutationTree<State> = {
state.handleElementId = handleElementId state.handleElementId = handleElementId
}, },
[MutationTypes.SET_ACTIVE_GROUP_ELEMENT_ID](state, activeGroupElementId: string) {
state.activeGroupElementId = activeGroupElementId
},
[MutationTypes.SET_CANVAS_PERCENTAGE](state, percentage: number) { [MutationTypes.SET_CANVAS_PERCENTAGE](state, percentage: number) {
state.canvasPercentage = percentage state.canvasPercentage = percentage
}, },

View File

@ -1,12 +1,14 @@
import { Slide, SlideTheme } from '@/types/slides' import { Slide, SlideTheme } from '@/types/slides'
import { CreatingElement } from '@/types/edit' import { CreatingElement } from '@/types/edit'
import { ToolbarState } from '@/types/toolbar' 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' import { SYS_FONTS } from '@/configs/font'
export interface State { export interface State {
activeElementIdList: string[]; activeElementIdList: string[];
handleElementId: string; handleElementId: string;
activeGroupElementId: string;
canvasPercentage: number; canvasPercentage: number;
canvasScale: number; canvasScale: number;
thumbnailsFocus: boolean; thumbnailsFocus: boolean;
@ -30,31 +32,27 @@ export interface State {
} }
export const state: State = { export const state: State = {
activeElementIdList: [], activeElementIdList: [], // 被选中的元素ID集合包含 handleElementId
handleElementId: '', handleElementId: '', // 正在操作的元素ID
canvasPercentage: 90, activeGroupElementId: '', // 组合元素成员中被选中可独立操作的元素ID
canvasScale: 1, canvasPercentage: 90, // 画布可视区域百分比
thumbnailsFocus: false, canvasScale: 1, // 画布缩放比例基于宽度1000px
editorAreaFocus: false, thumbnailsFocus: false, // 左侧导航缩略图区域聚焦
disableHotkeys: false, editorAreaFocus: false, // 编辑区域聚焦
showGridLines: false, disableHotkeys: false, // 禁用快捷键
creatingElement: null, showGridLines: false, // 显示网格线
availableFonts: [], creatingElement: null, // 正在插入的元素信息,需要绘制插入的元素需要(文字、形状、线条)
toolbarState: 'slideStyle', availableFonts: [], // 当前环境可用字体
theme: { toolbarState: 'slideStyle', // 右侧工具栏状态
themeColor: '#d14424', viewportRatio: 0.5625, // 可是区域比例默认16:9
fontColor: '#333', theme: theme, // 主题样式
fontName: '微软雅黑', slides: slides, // 幻灯片页面数据
backgroundColor: '#fff', slideIndex: 0, // 当前页面索引
}, selectedSlidesIndex: [], // 当前被选中的页面索引集合
viewportRatio: 0.5625, snapshotCursor: -1, // 历史快照指针
slides: slides, snapshotLength: 0, // 历史快照长度
slideIndex: 0, ctrlKeyState: false, // ctrl键按下状态
selectedSlidesIndex: [], shiftKeyState: false, // shift键按下状态
snapshotCursor: -1, screening: false, // 是否进入放映状态
snapshotLength: 0, clipingImageElementId: '', // 当前正在裁剪的图片ID
ctrlKeyState: false,
shiftKeyState: false,
screening: false,
clipingImageElementId: '',
} }

View File

@ -35,7 +35,7 @@ interface PPTBaseElement {
export interface PPTTextElement extends PPTBaseElement { export interface PPTTextElement extends PPTBaseElement {
type: 'text'; type: 'text';
content: string; content: string;
rotate?: number; rotate: number;
outline?: PPTElementOutline; outline?: PPTElementOutline;
fill?: string; fill?: string;
lineHeight?: number; lineHeight?: number;
@ -65,7 +65,7 @@ export interface PPTImageElement extends PPTBaseElement{
type: 'image'; type: 'image';
fixedRatio: boolean; fixedRatio: boolean;
src: string; src: string;
rotate?: number; rotate: number;
outline?: PPTElementOutline; outline?: PPTElementOutline;
filters?: ImageElementFilters; filters?: ImageElementFilters;
clip?: ImageElementClip; clip?: ImageElementClip;
@ -85,7 +85,7 @@ export interface PPTShapeElement extends PPTBaseElement{
fixedRatio: boolean; fixedRatio: boolean;
fill: string; fill: string;
gradient?: ShapeGradient; gradient?: ShapeGradient;
rotate?: number; rotate: number;
outline?: PPTElementOutline; outline?: PPTElementOutline;
opacity?: number; opacity?: number;
flip?: ImageOrShapeFlip; flip?: ImageOrShapeFlip;

View File

@ -30,16 +30,9 @@ export default defineComponent({
// 线 // 线
const gridColor = computed(() => { const gridColor = computed(() => {
if (!background.value || background.value.type === 'image') return 'rgba(100, 100, 100, 0.5)' const bgColor = background.value?.color || '#fff'
const color = background.value.color const colorList = ['#000', '#fff']
const rgba = tinycolor(color).toRgb() return tinycolor.mostReadable(bgColor, colorList, { includeFallbackColors: true }).setAlpha(.5).toRgbString()
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 gridSize = 50 const gridSize = 50
@ -80,5 +73,7 @@ export default defineComponent({
bottom: 0; bottom: 0;
right: 0; right: 0;
overflow: visible; overflow: visible;
z-index: 999;
pointer-events: none;
} }
</style> </style>

View File

@ -8,11 +8,11 @@ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
export default ( export default (
elementList: Ref<PPTElement[]>, elementList: Ref<PPTElement[]>,
activeGroupElementId: Ref<string>,
alignmentLines: Ref<AlignmentLineProps[]>, alignmentLines: Ref<AlignmentLineProps[]>,
) => { ) => {
const store = useStore() const store = useStore()
const activeElementIdList = computed(() => store.state.activeElementIdList) const activeElementIdList = computed(() => store.state.activeElementIdList)
const activeGroupElementId = computed(() => store.state.activeGroupElementId)
const canvasScale = computed(() => store.state.canvasScale) const canvasScale = computed(() => store.state.canvasScale)
const viewportRatio = computed(() => store.state.viewportRatio) const viewportRatio = computed(() => store.state.viewportRatio)

View File

@ -94,11 +94,11 @@ const getOppositePoint = (direction: string, points: ReturnType<typeof getRotate
export default ( export default (
elementList: Ref<PPTElement[]>, elementList: Ref<PPTElement[]>,
activeGroupElementId: Ref<string>,
alignmentLines: Ref<AlignmentLineProps[]>, alignmentLines: Ref<AlignmentLineProps[]>,
) => { ) => {
const store = useStore() const store = useStore()
const activeElementIdList = computed(() => store.state.activeElementIdList) const activeElementIdList = computed(() => store.state.activeElementIdList)
const activeGroupElementId = computed(() => store.state.activeGroupElementId)
const canvasScale = computed(() => store.state.canvasScale) const canvasScale = computed(() => store.state.canvasScale)
const viewportRatio = computed(() => store.state.viewportRatio) const viewportRatio = computed(() => store.state.viewportRatio)
const ctrlOrShiftKeyActive = computed<boolean>(() => store.getters.ctrlOrShiftKeyActive) const ctrlOrShiftKeyActive = computed<boolean>(() => store.getters.ctrlOrShiftKeyActive)

View File

@ -5,21 +5,22 @@ import { PPTElement } from '@/types/slides'
export default ( export default (
elementList: Ref<PPTElement[]>, elementList: Ref<PPTElement[]>,
activeGroupElementId: Ref<string>,
moveElement: (e: MouseEvent, element: PPTElement) => void, moveElement: (e: MouseEvent, element: PPTElement) => void,
) => { ) => {
const store = useStore() const store = useStore()
const activeElementIdList = computed(() => store.state.activeElementIdList) const activeElementIdList = computed(() => store.state.activeElementIdList)
const handleElementId = computed(() => store.state.handleElementId) const handleElementId = computed(() => store.state.handleElementId)
const activeGroupElementId = computed(() => store.state.activeGroupElementId)
const editorAreaFocus = computed(() => store.state.editorAreaFocus) const editorAreaFocus = computed(() => store.state.editorAreaFocus)
const ctrlOrShiftKeyActive = computed<boolean>(() => store.getters.ctrlOrShiftKeyActive) 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) if (!editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, true)
// 如果目标元素当前未被选中,则将他设为选中状态 // 如果目标元素当前未被选中,则将他设为选中状态
// 此时如果按下Ctrl键或Shift键则进入多选状态将当前已选中的元素和目标元素一设置为选中状态,否则仅将目标元素设置为选中状态 // 此时如果按下Ctrl键或Shift键则进入多选状态将当前已选中的元素和目标元素一设置为选中状态,否则仅将目标元素设置为选中状态
// 如果目标元素是分组成员,需要将该组合的其他元素一起设置为选中状态 // 如果目标元素是分组成员,需要将该组合的其他元素一起设置为选中状态
if (!activeElementIdList.value.includes(element.id)) { if (!activeElementIdList.value.includes(element.id)) {
let newActiveIdList: string[] = [] let newActiveIdList: string[] = []
@ -78,13 +79,13 @@ export default (
const currentPageY = e.pageY const currentPageY = e.pageY
if (startPageX === currentPageX && startPageY === currentPageY) { 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 ;(e.target as HTMLElement).onmouseup = null
} }
} }
} }
if (canMove) moveElement(e, element) if (startMove) moveElement(e, element)
} }
// 选中页面内的全部元素 // 选中页面内的全部元素

View File

@ -2,7 +2,7 @@
<div <div
class="canvas" class="canvas"
ref="canvasRef" ref="canvasRef"
@mousewheel="$event => mousewheelScaleCanvas($event)" @mousewheel="$event => handleMousewheelCanvas($event)"
@mousedown="$event => handleClickBlankArea($event)" @mousedown="$event => handleClickBlankArea($event)"
v-contextmenu="contextmenus" v-contextmenu="contextmenus"
v-click-outside="removeEditorAreaFocus" v-click-outside="removeEditorAreaFocus"
@ -82,6 +82,7 @@ import { ContextmenuItem } from '@/components/Contextmenu/types'
import { PPTElement, Slide } from '@/types/slides' import { PPTElement, Slide } from '@/types/slides'
import { AlignmentLineProps } from '@/types/edit' import { AlignmentLineProps } from '@/types/edit'
import { removeAllRanges } from '@/utils/selection' import { removeAllRanges } from '@/utils/selection'
import { KEYS } from '@/configs/hotkey'
import useViewportSize from './hooks/useViewportSize' import useViewportSize from './hooks/useViewportSize'
import useMouseSelection from './hooks/useMouseSelection' import useMouseSelection from './hooks/useMouseSelection'
@ -98,6 +99,7 @@ import useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'
import useSelectAllElement from '@/hooks/useSelectAllElement' import useSelectAllElement from '@/hooks/useSelectAllElement'
import useScaleCanvas from '@/hooks/useScaleCanvas' import useScaleCanvas from '@/hooks/useScaleCanvas'
import useScreening from '@/hooks/useScreening' import useScreening from '@/hooks/useScreening'
import useSlideHandler from '@/hooks/useSlideHandler'
import EditableElement from './EditableElement.vue' import EditableElement from './EditableElement.vue'
import MouseSelection from './MouseSelection.vue' import MouseSelection from './MouseSelection.vue'
@ -123,6 +125,7 @@ export default defineComponent({
const activeElementIdList = computed(() => store.state.activeElementIdList) const activeElementIdList = computed(() => store.state.activeElementIdList)
const handleElementId = computed(() => store.state.handleElementId) const handleElementId = computed(() => store.state.handleElementId)
const activeGroupElementId = computed(() => store.state.activeGroupElementId)
const editorAreaFocus = computed(() => store.state.editorAreaFocus) const editorAreaFocus = computed(() => store.state.editorAreaFocus)
const ctrlKeyState = computed(() => store.state.ctrlKeyState) const ctrlKeyState = computed(() => store.state.ctrlKeyState)
const ctrlOrShiftKeyActive = computed<boolean>(() => store.getters.ctrlOrShiftKeyActive) const ctrlOrShiftKeyActive = computed<boolean>(() => store.getters.ctrlOrShiftKeyActive)
@ -130,8 +133,9 @@ export default defineComponent({
const viewportRef = ref<HTMLElement>() const viewportRef = ref<HTMLElement>()
const alignmentLines = ref<AlignmentLineProps[]>([]) const alignmentLines = ref<AlignmentLineProps[]>([])
const activeGroupElementId = ref('') watch(handleElementId, () => {
watch(handleElementId, () => activeGroupElementId.value = '') store.commit(MutationTypes.SET_ACTIVE_GROUP_ELEMENT_ID, '')
})
const currentSlide = computed<Slide>(() => store.getters.currentSlide) const currentSlide = computed<Slide>(() => store.getters.currentSlide)
const elementList = ref<PPTElement[]>([]) const elementList = ref<PPTElement[]>([])
@ -148,16 +152,17 @@ export default defineComponent({
const { mouseSelectionState, updateMouseSelection } = useMouseSelection(elementList, viewportRef) const { mouseSelectionState, updateMouseSelection } = useMouseSelection(elementList, viewportRef)
const { dragElement } = useDragElement(elementList, activeGroupElementId, alignmentLines) const { dragElement } = useDragElement(elementList, alignmentLines)
const { dragLineElement } = useDragLineElement(elementList) const { dragLineElement } = useDragLineElement(elementList)
const { selectElement } = useSelectElement(elementList, activeGroupElementId, dragElement) const { selectElement } = useSelectElement(elementList, dragElement)
const { scaleElement, scaleMultiElement } = useScaleElement(elementList, activeGroupElementId, alignmentLines) const { scaleElement, scaleMultiElement } = useScaleElement(elementList, alignmentLines)
const { rotateElement } = useRotateElement(elementList, viewportRef) const { rotateElement } = useRotateElement(elementList, viewportRef)
const { selectAllElement } = useSelectAllElement() const { selectAllElement } = useSelectAllElement()
const { deleteAllElements } = useDeleteElement() const { deleteAllElements } = useDeleteElement()
const { pasteElement } = useCopyAndPasteElement() const { pasteElement } = useCopyAndPasteElement()
const { enterScreening } = useScreening() const { enterScreening } = useScreening()
const { updateSlideIndex } = useSlideHandler()
// //
const handleClickBlankArea = (e: MouseEvent) => { const handleClickBlankArea = (e: MouseEvent) => {
@ -172,17 +177,25 @@ export default defineComponent({
if (editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, false) if (editorAreaFocus.value) store.commit(MutationTypes.SET_EDITORAREA_FOCUS, false)
} }
// Ctrl //
const { scaleCanvas } = useScaleCanvas() const { scaleCanvas } = useScaleCanvas()
const throttleScaleCanvas = throttle(scaleCanvas, 100, { leading: true, trailing: false }) const throttleScaleCanvas = throttle(scaleCanvas, 100, { leading: true, trailing: false })
const throttleUpdateSlideIndex = throttle(updateSlideIndex, 300, { leading: true, trailing: false })
const mousewheelScaleCanvas = (e: WheelEvent) => { const handleMousewheelCanvas = (e: WheelEvent) => {
if (!ctrlKeyState.value) return
e.preventDefault() e.preventDefault()
// Ctrl
if (ctrlKeyState.value) {
if (e.deltaY > 0) throttleScaleCanvas('-') if (e.deltaY > 0) throttleScaleCanvas('-')
else 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)
}
}
// 线 // 线
const showGridLines = computed(() => store.state.showGridLines) const showGridLines = computed(() => store.state.showGridLines)
@ -247,7 +260,7 @@ export default defineComponent({
scaleElement, scaleElement,
dragLineElement, dragLineElement,
scaleMultiElement, scaleMultiElement,
mousewheelScaleCanvas, handleMousewheelCanvas,
contextmenus, contextmenus,
} }
}, },

View File

@ -186,6 +186,7 @@ export default defineComponent({
} }
.handler-item { .handler-item {
margin: 0 10px; margin: 0 10px;
font-size: 14px;
cursor: pointer; cursor: pointer;
&.disable { &.disable {
@ -202,8 +203,7 @@ export default defineComponent({
} }
.viewport-size { .viewport-size {
font-size: 12px; font-size: 13px;
margin-top: -1px;
} }
} }
</style> </style>

View File

@ -1,110 +1,35 @@
<template> <template>
<div class="export-dialog"> <div class="export-dialog">
<div class="tabs"> <div class="preview">
<div
class="tab"
:class="{ 'active': tab.value === currentTab }"
v-for="tab in tabs"
:key="tab.value"
@click="currentTab = tab.value"
>{{tab.label}}</div>
</div>
<div class="content json" v-if="currentTab === 'json'">
<div class="json-preview">
<pre>{{slides}}</pre> <pre>{{slides}}</pre>
</div> </div>
<div class="json-configs"> <div class="handle">
<Button class="btn" type="primary" @click="exportJSON()">导出 JSON 文件</Button> <Button class="btn" type="primary" @click="exportJSON()">导出</Button>
<Button class="btn" @click="emit('close')">关闭</Button> <Button class="btn" @click="emit('close')">关闭</Button>
</div> </div>
</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>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, ref } from 'vue' import { computed, defineComponent } from 'vue'
import { useStore } from '@/store' import { useStore } from '@/store'
import { saveAs } from 'file-saver' 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({ export default defineComponent({
name: 'export-dialog', name: 'export-dialog',
components: {
ThumbnailSlide,
},
setup(props, { emit }) { setup(props, { emit }) {
const store = useStore() const store = useStore()
const slides = computed(() => store.state.slides) 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 exportJSON = () => {
const blob = new Blob([JSON.stringify(slides.value)], { type: '' }) const blob = new Blob([JSON.stringify(slides.value)], { type: '' })
saveAs(blob, 'pptist_slides.json') 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 { return {
tabs,
currentTab,
spinning,
slides, slides,
exportJSON, exportJSON,
exportImage,
imageThumbnailsRef,
emit, emit,
} }
}, },
@ -114,40 +39,11 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.export-dialog { .export-dialog {
height: 500px; 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; display: flex;
justify-content: center; justify-content: center;
position: relative; position: relative;
overflow: hidden;
} }
.json-preview { .preview {
width: 460px; width: 460px;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
@ -160,7 +56,7 @@ export default defineComponent({
height: 100%; height: 100%;
} }
} }
.json-configs { .handle {
flex: 1; flex: 1;
.btn { .btn {
@ -168,52 +64,4 @@ export default defineComponent({
margin-bottom: 10px; 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> </style>

View File

@ -11,7 +11,7 @@
<MenuItem @click="deleteSlide()">删除页面</MenuItem> <MenuItem @click="deleteSlide()">删除页面</MenuItem>
<MenuItem @click="toggleGridLines()">{{ showGridLines ? '关闭网格线' : '打开网格线' }}</MenuItem> <MenuItem @click="toggleGridLines()">{{ showGridLines ? '关闭网格线' : '打开网格线' }}</MenuItem>
<MenuItem @click="resetSlides()">重置幻灯片</MenuItem> <MenuItem @click="resetSlides()">重置幻灯片</MenuItem>
<MenuItem @click="exportDialogVisible = true">导出</MenuItem> <MenuItem @click="exportDialogVisible = true">导出 JSON</MenuItem>
</Menu> </Menu>
</template> </template>
</Dropdown> </Dropdown>
@ -28,7 +28,6 @@
<div class="menu-item"><IconHelpcenter /> <span class="text">帮助</span></div> <div class="menu-item"><IconHelpcenter /> <span class="text">帮助</span></div>
<template #overlay> <template #overlay>
<Menu> <Menu>
<MenuItem @click="openDoc()">开发文档</MenuItem>
<MenuItem @click="hotkeyDrawerVisible = true">快捷键</MenuItem> <MenuItem @click="hotkeyDrawerVisible = true">快捷键</MenuItem>
</Menu> </Menu>
</template> </template>
@ -78,8 +77,6 @@ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
import HotkeyDoc from './HotkeyDoc.vue' import HotkeyDoc from './HotkeyDoc.vue'
import ExportDialog from './ExportDialog.vue' import ExportDialog from './ExportDialog.vue'
import { message } from 'ant-design-vue'
export default defineComponent({ export default defineComponent({
name: 'editor-header', name: 'editor-header',
components: { components: {
@ -98,10 +95,6 @@ export default defineComponent({
store.commit(MutationTypes.SET_GRID_LINES_STATE, !showGridLines.value) store.commit(MutationTypes.SET_GRID_LINES_STATE, !showGridLines.value)
} }
const openDoc = () => {
message.warning('作者努力编写中...')
}
const hotkeyDrawerVisible = ref(false) const hotkeyDrawerVisible = ref(false)
const exportDialogVisible = ref(false) const exportDialogVisible = ref(false)
@ -115,7 +108,6 @@ export default defineComponent({
toggleGridLines, toggleGridLines,
showGridLines, showGridLines,
resetSlides, resetSlides,
openDoc,
hotkeyDrawerVisible, hotkeyDrawerVisible,
exportDialogVisible, exportDialogVisible,
} }
@ -142,7 +134,7 @@ export default defineComponent({
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-size: 13px; font-size: 14px;
padding: 0 10px; padding: 0 10px;
transition: background-color .2s; transition: background-color .2s;
cursor: pointer; cursor: pointer;

View File

@ -5,7 +5,7 @@
v-click-outside="() => setThumbnailsFocus(false)" v-click-outside="() => setThumbnailsFocus(false)"
v-contextmenu="contextmenusThumbnails" v-contextmenu="contextmenusThumbnails"
> >
<div class="add-slide" @click="createSlide()"><IconPlus /> 添加幻灯片</div> <div class="add-slide" @click="createSlide()"><IconPlus class="icon" />添加幻灯片</div>
<Draggable <Draggable
class="thumbnail-list" class="thumbnail-list"
:modelValue="slides" :modelValue="slides"
@ -22,7 +22,7 @@
'active': slideIndex === index, 'active': slideIndex === index,
'selected': selectedSlidesIndex.includes(index), 'selected': selectedSlidesIndex.includes(index),
}" }"
@mousedown="handleClickSlideThumbnail(index)" @mousedown="$event => handleClickSlideThumbnail($event, index)"
v-contextmenu="contextmenusThumbnailItem" v-contextmenu="contextmenusThumbnailItem"
> >
<div class="label">{{ fillDigit(index + 1, 2) }}</div> <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 const isMultiSelected = selectedSlidesIndex.value.length > 1
if (isMultiSelected && selectedSlidesIndex.value.includes(index) && e.button !== 0) return
// Ctrl // Ctrl
if (ctrlKeyState.value) { if (ctrlKeyState.value) {
if (slideIndex.value === index) { if (slideIndex.value === index) {
@ -255,6 +257,11 @@ export default defineComponent({
flex-shrink: 0; flex-shrink: 0;
border-bottom: 1px solid $borderColor; border-bottom: 1px solid $borderColor;
cursor: pointer; cursor: pointer;
.icon {
margin-right: 3px;
font-size: 14px;
}
} }
.thumbnail-list { .thumbnail-list {
padding: 5px 0; padding: 5px 0;

View File

@ -129,7 +129,7 @@ export default defineComponent({
const themeColor = ref<string>('') const themeColor = ref<string>('')
const gridColor = ref('') const gridColor = ref('')
const lineSmooth = ref<boolean | Function>(true) const lineSmooth = ref(true)
const showLine = ref(true) const showLine = ref(true)
const showArea = ref(false) const showArea = ref(false)
const horizontalBars = ref(false) const horizontalBars = ref(false)
@ -148,7 +148,7 @@ export default defineComponent({
donut: _donut, donut: _donut,
} = handleElement.value.options } = handleElement.value.options
if (_lineSmooth !== undefined) lineSmooth.value = _lineSmooth if (_lineSmooth !== undefined) lineSmooth.value = _lineSmooth as boolean
if (_showLine !== undefined) showLine.value = _showLine if (_showLine !== undefined) showLine.value = _showLine
if (_showArea !== undefined) showArea.value = _showArea if (_showArea !== undefined) showArea.value = _showArea
if (_horizontalBars !== undefined) horizontalBars.value = _horizontalBars if (_horizontalBars !== undefined) horizontalBars.value = _horizontalBars

View File

@ -2,24 +2,24 @@
<div class="multi-position-panel"> <div class="multi-position-panel">
<ButtonGroup class="row"> <ButtonGroup class="row">
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="左对齐"> <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>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="水平居中"> <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>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="右对齐"> <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> </Tooltip>
</ButtonGroup> </ButtonGroup>
<ButtonGroup class="row"> <ButtonGroup class="row">
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="上对齐"> <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>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="垂直居中"> <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>
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="下对齐"> <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> </Tooltip>
</ButtonGroup> </ButtonGroup>
<ButtonGroup class="row" v-if="activeElementList.length > 2"> <ButtonGroup class="row" v-if="activeElementList.length > 2">
@ -40,8 +40,10 @@
import { computed, defineComponent } from 'vue' import { computed, defineComponent } from 'vue'
import { useStore } from '@/store' import { useStore } from '@/store'
import { PPTElement } from '@/types/slides' import { PPTElement } from '@/types/slides'
import { ElementAlignCommand } from '@/types/edit'
import useCombineElement from '@/hooks/useCombineElement' import useCombineElement from '@/hooks/useCombineElement'
import useAlignActiveElement from '@/hooks/useAlignActiveElement' import useAlignActiveElement from '@/hooks/useAlignActiveElement'
import useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas'
import useUniformDisplayElement from '@/hooks/useUniformDisplayElement' import useUniformDisplayElement from '@/hooks/useUniformDisplayElement'
export default defineComponent({ export default defineComponent({
@ -52,6 +54,7 @@ export default defineComponent({
const { combineElements, uncombineElements } = useCombineElement() const { combineElements, uncombineElements } = useCombineElement()
const { alignActiveElement } = useAlignActiveElement() const { alignActiveElement } = useAlignActiveElement()
const { alignElementToCanvas } = useAlignElementToCanvas()
const { uniformHorizontalDisplay, uniformVerticalDisplay } = useUniformDisplayElement() const { uniformHorizontalDisplay, uniformVerticalDisplay } = useUniformDisplayElement()
// //
@ -63,14 +66,22 @@ export default defineComponent({
return !inSameGroup return !inSameGroup
}) })
//
//
//
const alignElement = (command: ElementAlignCommand) => {
if (canCombine.value) alignActiveElement(command)
else alignElementToCanvas(command)
}
return { return {
activeElementList, activeElementList,
canCombine, canCombine,
combineElements, combineElements,
uncombineElements, uncombineElements,
alignActiveElement,
uniformHorizontalDisplay, uniformHorizontalDisplay,
uniformVerticalDisplay, uniformVerticalDisplay,
alignElement,
} }
}, },
}) })

View File

@ -9,9 +9,15 @@
</teleport> </teleport>
<div class="tools"> <div class="tools">
<div class="btn" :class="{ 'active': writingBoardModel === 'pen' }" @click="changePen()">画笔</div> <Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.3" title="画笔">
<div class="btn" :class="{ 'active': writingBoardModel === 'eraser' }" @click="changeEraser()">橡皮</div> <div class="btn" :class="{ 'active': writingBoardModel === 'pen' }" @click="changePen()"><IconWrite class="icon" /></div>
<div class="btn" @click="clearCanvas()">清除墨迹</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="colors">
<div <div
class="color" class="color"
@ -22,7 +28,9 @@
@click="changeColor(color)" @click="changeColor(color)"
></div> ></div>
</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>
</div> </div>
</template> </template>
@ -91,41 +99,49 @@ export default defineComponent({
.tools { .tools {
height: 50px; height: 50px;
position: fixed; position: fixed;
bottom: 0; bottom: 5px;
left: 0; left: 5px;
z-index: 11; z-index: 11;
padding: 12px; padding: 12px;
background-color: #fff; background-color: #eee;
border-radius: $borderRadius;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.btn { .btn {
padding: 6px 10px; padding: 5px 10px;
cursor: pointer; cursor: pointer;
&:hover, &.active { &:hover {
background-color: rgba($color: $themeColor, $alpha: .2); color: $themeColor;
} }
&.active {
background-color: rgba($color: $themeColor, $alpha: .5);
color: #fff;
}
}
.icon {
font-size: 20px;
} }
.colors { .colors {
display: flex; display: flex;
padding: 0 10px; padding: 0 10px;
} }
.color { .color {
width: 15px; width: 16px;
height: 15px; height: 16px;
outline: 1px solid #ccc; border-radius: $borderRadius;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
transform: scale(1.1); transform: scale(1.15);
} }
&.active { &.active {
outline: 2px solid $themeColor; transform: scale(1.3);
} }
& + .color { & + .color {
margin-left: 5px; margin-left: 8px;
} }
} }
} }

View File

@ -422,15 +422,9 @@ export default defineComponent({
right: 8px; right: 8px;
padding: 8px 12px; padding: 8px 12px;
color: #666; color: #666;
border: 2px solid #acacac; background-color: #eee;
background-color: rgba($color: #fff, $alpha: .6);
border-radius: $borderRadius; border-radius: $borderRadius;
z-index: 10; z-index: 10;
cursor: pointer; cursor: pointer;
&:hover {
border: 2px solid #333;
color: #333;
}
} }
</style> </style>

View File

@ -7,7 +7,6 @@
width: elementInfo.width + 'px', width: elementInfo.width + 'px',
height: elementInfo.height + 'px', height: elementInfo.height + 'px',
}" }"
@mousedown="$event => handleSelectElement($event)"
> >
<div <div
class="element-content" class="element-content"
@ -15,6 +14,7 @@
backgroundColor: elementInfo.fill, backgroundColor: elementInfo.fill,
}" }"
v-contextmenu="contextmenus" v-contextmenu="contextmenus"
@mousedown="$event => handleSelectElement($event)"
> >
<ElementOutline <ElementOutline
:width="elementInfo.width" :width="elementInfo.width"
@ -79,7 +79,6 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.editable-element-chart { .editable-element-chart {
position: absolute; position: absolute;
cursor: move;
&.lock .element-content { &.lock .element-content {
cursor: default; cursor: default;
@ -90,5 +89,6 @@ export default defineComponent({
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
cursor: move;
} }
</style> </style>

View File

@ -8,7 +8,6 @@
width: elementInfo.width + 'px', width: elementInfo.width + 'px',
height: elementInfo.height + 'px', height: elementInfo.height + 'px',
}" }"
@mousedown="$event => handleSelectElement($event)"
> >
<div <div
class="rotate-wrapper" class="rotate-wrapper"
@ -28,11 +27,12 @@
<div <div
class="element-content" class="element-content"
v-else v-else
v-contextmenu="contextmenus"
:style="{ :style="{
filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '', filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
transform: flipStyle, transform: flipStyle,
}" }"
v-contextmenu="contextmenus"
@mousedown="$event => handleSelectElement($event)"
> >
<ImageOutline :elementInfo="elementInfo" /> <ImageOutline :elementInfo="elementInfo" />

View File

@ -8,7 +8,6 @@
width: elementInfo.width + 'px', width: elementInfo.width + 'px',
height: elementInfo.height + 'px', height: elementInfo.height + 'px',
}" }"
@mousedown="$event => handleSelectElement($event)"
> >
<div <div
class="rotate-wrapper" class="rotate-wrapper"
@ -16,12 +15,13 @@
> >
<div <div
class="element-content" class="element-content"
v-contextmenu="contextmenus"
:style="{ :style="{
opacity: elementInfo.opacity, opacity: elementInfo.opacity,
filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '', filter: shadowStyle ? `drop-shadow(${shadowStyle})` : '',
transform: flipStyle, transform: flipStyle,
}" }"
v-contextmenu="contextmenus"
@mousedown="$event => handleSelectElement($event)"
> >
<SvgWrapper <SvgWrapper
overflow="visible" overflow="visible"
@ -118,7 +118,6 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.editable-element-shape { .editable-element-shape {
position: absolute; position: absolute;
cursor: move;
&.lock .element-content { &.lock .element-content {
cursor: default; cursor: default;
@ -132,6 +131,7 @@ export default defineComponent({
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative; position: relative;
cursor: move;
svg { svg {
transform-origin: 0 0; transform-origin: 0 0;

View File

@ -103,6 +103,8 @@ export default defineComponent({
const realHeightCache = ref(-1) const realHeightCache = ref(-1)
const scaleElementStateListener = (state: boolean) => { const scaleElementStateListener = (state: boolean) => {
if (handleElementId.value !== props.elementInfo.id) return
isScaling.value = state isScaling.value = state
if (state) editable.value = false if (state) editable.value = false
@ -190,7 +192,6 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.editable-element-table { .editable-element-table {
position: absolute; position: absolute;
cursor: move;
&.lock .element-content { &.lock .element-content {
cursor: default; cursor: default;
@ -201,6 +202,7 @@ export default defineComponent({
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative; position: relative;
cursor: move;
} }
.table-mask { .table-mask {
position: absolute; position: absolute;

View File

@ -8,7 +8,6 @@
left: elementInfo.left + 'px', left: elementInfo.left + 'px',
width: elementInfo.width + 'px', width: elementInfo.width + 'px',
}" }"
@mousedown="$event => handleSelectElement($event)"
> >
<div <div
class="rotate-wrapper" class="rotate-wrapper"
@ -24,6 +23,7 @@
letterSpacing: (elementInfo.wordSpace || 0) + 'px', letterSpacing: (elementInfo.wordSpace || 0) + 'px',
}" }"
v-contextmenu="contextmenus" v-contextmenu="contextmenus"
@mousedown="$event => handleSelectElement($event)"
> >
<ElementOutline <ElementOutline
:width="elementInfo.width" :width="elementInfo.width"
@ -108,6 +108,8 @@ export default defineComponent({
// vuex // vuex
// //
const scaleElementStateListener = (state: boolean) => { const scaleElementStateListener = (state: boolean) => {
if (handleElementId.value !== props.elementInfo.id) return
isScaling.value = state isScaling.value = state
if (!state && realHeightCache.value !== -1) { if (!state && realHeightCache.value !== -1) {
@ -323,7 +325,6 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.editable-element-text { .editable-element-text {
position: absolute; position: absolute;
cursor: move;
&.lock .element-content { &.lock .element-content {
cursor: default; cursor: default;
@ -338,6 +339,7 @@ export default defineComponent({
padding: 10px; padding: 10px;
line-height: 1.5; line-height: 1.5;
word-break: break-word; word-break: break-word;
cursor: move;
.text { .text {
position: relative; position: relative;