refactor: re1.0

main
咬轮猫 3 years ago
parent 1b375106c9
commit d94609d1f4

@ -0,0 +1,5 @@
*.md
.vscode
.idea
dist
node_modules

@ -0,0 +1,59 @@
module.exports = {
root: true,
env: {
browser: true,
node: true,
es6: true
},
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 'latest',
sourceType: 'module',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true
}
},
extends: [
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'plugin:prettier/recommended' // 一定要放在最后。因为 extends 中后引入的规则会覆盖前面的规则。
],
rules: {
// @typescript-eslint
'@typescript-eslint/explicit-function-return-type': 'off', // 需要函数和类方法的显式返回类型
'@typescript-eslint/no-explicit-any': 'off', // 禁止使用该 any 类型
'@typescript-eslint/no-var-requires': 'off', // 不允许使用 require 语句,除了在 import 语句中
'@typescript-eslint/no-empty-function': 'off', // 禁止空函数
'@typescript-eslint/no-use-before-define': 'off', // 在定义之前禁止使用变量
'@typescript-eslint/ban-ts-comment': 'off', // 禁止 @ts-<directive> 使用评论或在指令后要求描述
'@typescript-eslint/ban-types': 'off', // 禁止使用特定类型
'@typescript-eslint/no-non-null-assertion': 'off', // '!'不允许使用后缀运算符的非空断言
'@typescript-eslint/explicit-module-boundary-types': 'off', // 需要导出函数和类的公共类方法的显式返回和参数类型
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_'
}
], // 禁止未使用的变量
// vue
'vue/custom-event-name-casing': 'off', // 为自定义事件名称强制使用特定大小写
'vue/attributes-order': 'off', // 强制执行属性顺序
'vue/one-component-per-file': 'off', // 强制每个组件都应该在自己的文件中
'vue/html-closing-bracket-newline': 'off', // 在标签的右括号之前要求或禁止换行
'vue/multiline-html-element-content-newline': 'off', // 在多行元素的内容之前和之后需要换行符
'vue/singleline-html-element-content-newline': 'off', // 在单行元素的内容之前和之后需要换行符
'vue/attribute-hyphenation': 'off', // 对模板中的自定义组件强制执行属性命名样式
'vue/require-default-prop': 'off', // 需要 props 的默认值
'vue/html-indent': ['error', 2], // 在<template>中强制一致缩进
'vue/html-self-closing': 'off', // 执行自闭合的风格
'vue/max-attributes-per-line': 'off', // 强制每行属性的最大数量
'vue/multi-word-component-names': 'off', // 是否开启组件命名规则校验(强制多个单词以驼峰或'-'链接的命名规则)
// ESLint
'no-use-before-define': 'off', // 禁止在变量定义之前使用它们
'space-before-function-paren': 'off' // 强制在 function的左括号之前使用一致的空格
}
};

19
.gitignore vendored

@ -1,5 +1,22 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
*.local
# Editor directories and files
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

1
.husky/.gitignore vendored

@ -0,0 +1 @@
_

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no -- commitlint --edit $1

@ -0,0 +1,9 @@
#!/bin/sh
command_exists () {
command -v "$1" >/dev/null 2>&1
}
# Workaround for Windows 10, Git Bash and Yarn
if command_exists winpty && test -t 1; then
exec < /dev/tty
fi

@ -0,0 +1,5 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
npx lint-staged

@ -0,0 +1,3 @@
/node_modules/**
/dist*
/public/*

@ -1,3 +1,3 @@
{
"recommendations": ["johnsoncodehk.volar"]
"recommendations": ["Vue.volar"]
}

@ -0,0 +1,7 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"eslint.autoFixOnSave": true
},
"files.eol": "\n"
}

@ -1,287 +1,159 @@
# vue-webtopo-svgeditor
# 重制版正在开发中请关注分支Re-1.0,预览地址[Re-1.0](http://re.svg.yaolm.top/),目前进展顺利,不久会替换掉当前分支代码
> 基于vue3.2+ts实现的svg可视化web组态编辑器。可直接把svg文件和vue组件作为编辑器图形库使用赋予其缩放和旋转等功能并支持自定义拓展参数实时控制组件状态等
> 基于vue3.2+ts实现的svg可视化web组态编辑器。可无需修改代码动态添加组件、自定义拓展组件参数
qq交流群209048413
当前主分支版本为3.0,该项目基于vue3.2+ts开发,ui使用了[naiveui](https://www.naiveui.com/zh-CN/os-theme)
预览地址:[http://svg.yaolm.top/](http://svg.yaolm.top/)
## [本文图片挂了请点此查看](https://www.cnblogs.com/Hero-/p/14784744.html)
`qq`交流群:`209048413`,仅供交流,有问题请提[issue](https://github.com/yaolunmao/vue-webtopo-svgeditor/issues),以便帮助更多有相同问题的人
## 预览地址
历史版本请在本项目其它分支进行查看
[http://svg.yaolm.top](http://svg.yaolm.top)(请使用版本新一点的浏览器🤔)
## 项目优点
## git地址
https://github.com/yaolunmao/vue-webtopo-svgeditor
### 组态软件核心功能都具备
https://gitee.com/yaolunmao/vue-webtopo-svgeditor
没有特殊需求,只需要配置好图形库,即可开始您的组态世界
## 这个项目都能干什么
### 优越的性能
目前我用来绘制物联网监控系统图
使用了`svg`的`symbol`技术,`use`方式加载`svg`图形同样的svg图形不管数量多少只会渲染一次
电力监控系统图
### 学习成本极低
![电力监控系统图](https://images.cnblogs.com/cnblogs_com/Hero-/2077550/o_211211075750_%E7%94%B5%E5%8A%9B%E9%A2%84%E8%A7%88.gif)
`svg`文件即组件,引入之后无需进行额外配置,编辑器会自适应解析加载组件,添加自定义组件和传统`html`无差前端er零学习成本上手
水务系统图
### 易拓展
![水务系统图](https://images.cnblogs.com/cnblogs_com/Hero-/2077550/o_211222131344_shuiwu.gif)
配置文件采用开放式结构,属性支持自定义拓展。图形库支持添加`svg`文件或者`vue`组件,定制开发将变得容易
但不仅仅局限于这些只要是svg矢量图标都可以作为组件进行拖拽组态
### 易于集成
项目已经编写好了库模式脚本,组件已经发布到[npm](https://www.npmjs.com/package/webtopo-svg-edit),支持外部传入自定义组件,支持组件事件订阅等
### 免费开源
## 优点
MIT开源协议 可商用(自带的`svg`文件除外)
- **学习成本极低**核心代码仅500多行
- **添加组件无需修改代码**只要在json里新增一项就可
- **轻量**组件打包只有1MB
- **易拓展**按照我定义好的格式只需要在json里面定义好属性便可以动态修改svg组件的任意节点
- **易于集成**组件已经发布到npm只需要引用到现有项目将组件数据传给组件即可使用
- **免费**MIT开源协议 可商用
## 如何引用
## 图形库说明
```
# 创建项目(已有项目跳过此步骤)
npm init vite@latest
# 进入项目目录
cd projectname
### 无状态组件
# 安装插件
npm install vue-webtopo-svgeditor
#引入插件
import {SvgEditor,SvgPrview} from 'vue-webtopo-svgeditor';
import 'vue-webtopo-svgeditor/dist/style.css'
# 修改vite.config.ts使之可以进行运行时创建组件
alias: {
'vue': 'vue/dist/vue.esm-bundler.js' // 定义vue的别名如果使用其他的插件可能会用到别名
},
# 如果需要使用图表 修改main.ts 全局注册一下图表组件
import { VChart} from 'vue-webtopo-svgeditor';
const app=createApp(App)
app.component('v-chart',VChart)
app.mount('#app')
```
无状态组件就是`svg`文件,编辑器已经将节点的拖动,缩放,旋转等功能封装好了,您无需做出额外配置即可使用,若是需要动态的去设置`svg`的颜色边框之类的,需要自行设置`props`,参考`src\config-center\svg-file\stateless`的配置,可以设置的属性为`svg`的公共属性,例如`fill`、`stroke `、`stroke-dasharray`这些,具体属性请参考[MDN-SVG属性](https://developer.mozilla.org/zh-CN/docs/Web/SVG/Attribute#svg_%E5%B1%9E%E6%80%A7%EF%BC%88%E6%8C%89%E7%B1%BB%E5%88%AB%E5%88%86%E7%B1%BB%EF%BC%89)
## 如何使用
使用编辑器需要将配置好的插件列表传递给组件,插件列表可通过调用后台接口的方式或者定义在项目里,推荐使用接口方式,可使项目更加灵活,举例`demo.json`
```json
[{
"type": "TestAddSvg",//组件类型 可理解为组件的唯一id
"title": "测试新增心形",//组件名称
"panel_class": "common",//组件在左侧工具栏的分类
"template": "<path :fill=\"prop_data.extend_attr.color.val\" :stroke=\"prop_data.extend_attr.color.val\" stroke-width=\"5\" style=\"pointer-events:inherit\" d=\"m143.72081869586242,163.35565803158485 c14.617751633754164,-41.93617271978648 71.89058180534832,0 0,53.91793635401125 c-71.89058180534832,-53.91793635401125 -14.617751633754164,-95.85410907379776 0,-53.91793635401125 z\" fill-opacity=\"1\" stroke-opacity=\"1\" transform=\"translate(-145,-180)\"></path> ",//svg元素
"props": [
"prop_data"
],
"extend_attr": {//拓展参数
"color": {
"title": "心形颜色",
"val": "#FF0000",
"type": "colorinputbox"
}
},
"create_type": "draggable",//创建方式 拖动还是绘制
"priview_img": "http://svg.yaolm.top/test.png"//左侧工具栏的预览图像
}
]
```
### 有状态组件
更多组件请参考`public\InterfaceReturn.json`
有状态组件也是svg文件区别就在于无状态组件控制节点属性的配置配置在`props`上,而有状态组件的配置需要配置到`state`上,有状态组件对比无状态组件的好处是可以改变一个状态去影响多个属性,例如这里的断路器开关,其实是同时改变了颜色和把手的透明度,参考配置:`src\config-center\svg-file\stateful\circuit-breaker\index.ts`
在vue里引入编辑器组件然后将`demo.json`传递给组件的`component_infos`
![](./readme-imgs/有状态组件.gif)
```vue
<svg-editor
:component_infos="demo.json"
@saveSvgInfo="saveSvgInfo"
:svgCanvas="{ width: 1920, height: 1080 }"
></svg-editor>
```
### 自带动画组件
| 属性名 | 说明 | 默认值 |
| --------------- | ------------------------ | ---------------------------- |
| component_infos | 预定义的组件列表 | |
| saveSvgInfo | 编辑器点击保存的回调方法 | |
| svgCanvas | 画板的大小 | { width: 1520, height: 720 } |
没什么好说的了,支持直接使用自带动画的`svg`文件,参考`src\config-center\svg-file\have-animation`
预览组件同理
### 自定义svg
```vue
<svg-prview
:component_infos="demo.json"
:svg_data="prview_data"
:svgCanvas="{ width: 1920, height: 1080 }"
></svg-prview>
```
本质上就是`vue`的`template`代码片段,它可以完全替代无状态组件和有状态组件,并且可以根据自定义代码实现更多节点和属性的控制,缺点就是需要写代码--!参考`src\config-center\svg-file\custom-svg`
| 属性名 | 说明 | 默认值 |
| --------------- | -------------------- | ---------------------------- |
| component_infos | 预定义的组件列表 | |
| svg_data | 编辑器点击保存的数据 | |
| svgCanvas | 画板的大小 | { width: 1520, height: 720 } |
### vue组件
## 如何进行开发
```
# 克隆项目
git clone https://github.com/yaolunmao/vue-webtopo-svgeditor.git
# 进入项目目录
cd vue-webtopo-svgeditor
# 安装依赖
npm install
# 启动服务
npm run dev
```
其实和自定义`svg`相似,都是`vue`的`template`代码片段,但是`svg`标签里面无法渲染`html`的代码,所以在最外层用了`foreignObject`进行了包裹,同样支持缩放旋转等操作。参考`src\config-center\vue`
## 操作说明
- 常规组件和图表组件用鼠标左键选中并按住可拖动至画布
- 绘制组件需先选中 再将鼠标移至画布中按住左键开始绘制 松开左键结束绘制
- 在画布上单击左键并按住可以拖动组件改变位置
- 当组件选中时会有选中效果 此时右侧面板弹出 可使用快捷键和编辑右侧面板属性更改组件
- 键盘↑↓←→可移动选中组件 ctrl+c复制当前选中组件 deleted删除当前选中组件
- 点击画布空白处可以取消选中组件 并关闭属性面板
- 点击《 符号可以显示或隐藏左侧面板
### 绘画
## 动态添加组件
选中左侧的组件库,按住鼠标左键即可把组件拖动到画布中
可使用任意生成svg代码的工具我这里使用在线编译器进行模拟
![](./readme-imgs/绘画.gif)
点击[这里](http://svgedit.yaolm.top/)进行svg图像绘制我这里以心形为例
## 操作
![绘制心形图片](https://images.cnblogs.com/cnblogs_com/Hero-/1976969/o_2105190646421.png)
选中绘制好的节点后会出现锚点,可以直接进行移动、缩放、旋转等功能,右侧属性面板可以设置配置好的节点的属性,鼠标右键可以进行一些快捷操作
将svg代码复制下来双引号进行转义删除无用属性比如id你也可以直接使用我下面的代码
```
<path fill=\"#FF0000\" stroke=\"#000000\" stroke-width=\"5\" style=\"pointer-events:inherit\" d=\"m143.72081869586242,163.35565803158485 c14.617751633754164,-41.93617271978648 71.89058180534832,0 0,53.91793635401125 c-71.89058180534832,-53.91793635401125 -14.617751633754164,-95.85410907379776 0,-53.91793635401125 z\" fill-opacity=\"1\" stroke-opacity=\"1\"></path>
```
![](./readme-imgs/操作.gif)
将拓展字段进行双向绑定 我目前只做了颜色
### 连线
```
<path :fill=\"prop_data.extend_attr.color.val\" :stroke=\"prop_data.extend_attr.color.val\" stroke-width=\"5\" style=\"pointer-events:inherit\" d=\"m143.72081869586242,163.35565803158485 c14.617751633754164,-41.93617271978648 71.89058180534832,0 0,53.91793635401125 c-71.89058180534832,-53.91793635401125 -14.617751633754164,-95.85410907379776 0,-53.91793635401125 z\" fill-opacity=\"1\" stroke-opacity=\"1\" transform=\"translate(-145,-180)\"></path>
```
鼠标移动到组件上时会出现连线锚点,左键点击锚点创建线段,继续左键点击画布会连续创建线段,右键停止创建线段,鼠标放在线段上会出现线段端点提示,拖动即可重新设置连线,选中线段后还可以在右侧的动画面板设置线段的动画效果
修改项目文件夹pubilc下的模拟接口返回的json新增一项
```json
{
"type": "TestAddSvg",
"title": "测试新增心形",
"panel_class": "common",
"template": "<path :fill=\"prop_data.extend_attr.color.val\" :stroke=\"prop_data.extend_attr.color.val\" stroke-width=\"5\" style=\"pointer-events:inherit\" d=\"m143.72081869586242,163.35565803158485 c14.617751633754164,-41.93617271978648 71.89058180534832,0 0,53.91793635401125 c-71.89058180534832,-53.91793635401125 -14.617751633754164,-95.85410907379776 0,-53.91793635401125 z\" fill-opacity=\"1\" stroke-opacity=\"1\" transform=\"translate(-145,-180)\"></path>",
"props": [
"prop_data"
],
"extend_attr": {
"color": {
"title": "心形颜色",
"val": "#FF0000",
"type": "colorinputbox"
}
},
"create_type": "draggable",
"priview_img": "http://svg.yaolm.top/test.png"
}
```
![](./readme-imgs/连线.gif)
启动项目,就可以看到刚才添加的组件了
## 其他说明
![预览界面](https://images.cnblogs.com/cnblogs_com/Hero-/1976969/o_211031113925_%E5%8A%A8%E6%80%81%E6%96%B0%E5%A2%9E%E7%9A%84%E7%BB%84%E4%BB%B6.gif)
- 若是组件中心点不是鼠标指针点,请自行在页面设置里匹配`x`轴和`y`轴数据
- 组件隐藏后,可以在顶部工具栏的组件树里面选中隐藏的组件,进而设置隐藏组件的属性
- 本项目示例`svg`文件规格以标准图标大小`1024x1024px`,如果您的组件效果不好,请使用该规格
也支持直接引入图片只不过放大有失真将下面的代码替换上面json文件的template值
## 集成到已有项目
```
<image x=\"-33\" y=\"-33\" width=\"66\" height=\"66\" xlink:href=\"http://svg.yaolm.top/test.png\" />
```
ps目前中心辅助线的坐标取决于svg组件的中心坐标请自行添加transform属性调整svg组件中心坐标
## 右侧属性面板的自定义属性说明
因为组件的多样性,所以增加了自定义拓展属性。例如文字组件可输入文字内容、修改字体样式等,开关组件则可以进行闭合,使用拓展属性可以针对某个组件显示不同的样式。
目前只封装了6种
- colorinputbox:颜色选择器
- radiogroup单选
- numberinputbox数字输入框
- textinputbox文本输入框
- textareainputbox文本域
- select下拉选择器
具体配置可参考文件`public\InterfaceReturn.json`查看对象的`extend_attr`属性
# 创建项目(已有项目跳过此步骤)
npm init vite@latest
## 关于vue2的特别说明
# 进入项目目录
cd projectname
vue2想使用动态组件可以使用`Vue.component()`,此处感谢大佬[chj2020](https://github.com/chj2020)指导
# 安装插件
pnpm i webtopo-svg-edit
### vue2如何支持多节点
# 安装pinia
pnpm i pinia
首先安装vue-fragment
# 修改main.ts 注册pinia
import { createPinia } from 'pinia';
const app = createApp(App);
app.use(createPinia());
app.mount('#app')
```vue
npm install vue-fragment
#在需要的页面引入插件
import { WebtopoSvgEdit,WebtopoSvgPreview } from 'webtopo-svg-edit';
import 'webtopo-svg-edit/dist/style.css'
```
引用vue-fragment
如果集成有问题请参考示例项目[vue-webtopo-svgeditor-example](https://github.com/yaolunmao/vue-webtopo-svgeditor-example)
```vue
//main.js
import Fragment from 'vue-fragment'
Vue.use(Fragment.Plugin)
```
**请注意插件方式引入会导致左侧工具栏的icon图标无法正确显示请自行寻找您项目构建工具的svg加载器将icon图标转换成symbol并将名字命名为svg-xxx即可正常显示**
然后在json文件的template属性值里面最外层使用标签
## 贡献代码
```vue
<fragment></fragment>
```
1. `Fork` 本项目
2. 新建` Feat_xxx `分支
3. 提交代码
4. 新建 `Pull Request`
例如
```vue
<fragment><path :fill=\"prop_data.extend_attr.color.val\" :stroke=\"prop_data.extend_attr.color.val\" stroke-width=\"5\" style=\"pointer-events:inherit\" d=\"m143.72081869586242,163.35565803158485 c14.617751633754164,-41.93617271978648 71.89058180534832,0 0,53.91793635401125 c-71.89058180534832,-53.91793635401125 -14.617751633754164,-95.85410907379776 0,-53.91793635401125 z\" fill-opacity=\"1\" stroke-opacity=\"1\" transform=\"translate(-145,-180)\"></path></fragment>
```
**只接受github的prgitee为github镜像库**
## 声明
## 截图
**本项目组件库来源均为网络,仅供学习交流使用,请勿将本项目里面的组件用于商业用途**
## 君子协议
开源版使用时请保留底部的版权声明,感谢支持
## ![常规组件](https://images.cnblogs.com/cnblogs_com/Hero-/1976969/o_211031114029_%E5%B8%B8%E8%A7%84%E7%BB%84%E4%BB%B6.gif)
![](./readme-imgs/版权声明.png)
![绘制组件](https://images.cnblogs.com/cnblogs_com/Hero-/1976969/o_211031114035_%E7%BB%98%E5%88%B6%E7%BB%84%E4%BB%B6.gif)
## 常见问题
![图表组件](https://images.cnblogs.com/cnblogs_com/Hero-/1976969/o_211031114040_%E5%9B%BE%E8%A1%A8%E7%BB%84%E4%BB%B6.gif)
行尾序列问题error Delete `␍` prettier/prettier
`git config --global core.autocrlf false`
![修改组件属性](https://images.cnblogs.com/cnblogs_com/Hero-/1976969/o_211031114045_%E4%BF%AE%E6%94%B9%E7%BB%84%E4%BB%B6%E5%B1%9E%E6%80%A7.gif)
## 捐助
![](https://images.cnblogs.com/cnblogs_com/Hero-/2077550/o_211211080131_%E5%8A%A8%E7%94%BB%E7%BB%98%E5%88%B6.gif)
如果这个项目对您有所帮助,请扫下方二维码打赏一杯咖啡。
![预览界面](https://images.cnblogs.com/cnblogs_com/Hero-/2077550/o_211211075750_%E7%94%B5%E5%8A%9B%E9%A2%84%E8%A7%88.gif)
![](./readme-imgs/捐赠.jpg)
## License
## 感谢以下小伙伴为此项目做出的贡献
[MIT](http://opensource.org/licenses/MIT)
<a href="https://github.com/yaolunmao/vue-webtopo-svgeditor/graphs/contributors"><img src="https://contrib.rocks/image?repo=yaolunmao/vue-webtopo-svgeditor" /></a>

@ -0,0 +1 @@
module.exports = { extends: ['@commitlint/config-conventional'] };

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vue-webtopo-svgeditor</title>
<title>webtopo-svg-editor</title>
</head>
<body>
<div id="app"></div>

1587
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,15 +1,17 @@
{
"name": "vue-webtopo-svgeditor",
"version": "0.0.4",
"name": "webtopo-svg-edit",
"version": "0.0.2",
"description": "基于vue3.2+ts实现的svg可视化web组态编辑器。",
"author": "咬轮猫 <10928033@qq.com>",
"files": [
"dist"
],
"module": "./dist/vue-webtopo-svgeditor.es.ts",
"main": "./dist/vue-webtopo-svgeditor.umd.ts",
"module": "./dist/webtopo-svg-edit.es.ts",
"main": "./dist/webtopo-svg-edit.umd.ts",
"exports": {
".": {
"import": "./dist/vue-webtopo-svgeditor.es.ts",
"require": "./dist/vue-webtopo-svgeditor.umd.ts"
"import": "./dist/webtopo-svg-edit.es.ts",
"require": "./dist/webtopo-svg-edit.umd.ts"
},
"./dist/style.css": {
"import": "./dist/style.css",
@ -17,23 +19,50 @@
}
},
"scripts": {
"dev": "vite",
"dev": "vite --port 3001",
"build": "vue-tsc --noEmit && vite build",
"serve": "vite preview"
"preview": "vite preview",
"lint:eslint": "eslint --fix --ext .js,.ts,.vue,.tsx ./src",
"preinstall": "npx only-allow pnpm",
"postinstall": "husky install",
"lib": "vue-tsc --noEmit --skipLibCheck && vite build --mode lib"
},
"dependencies": {
"axios": "^0.22.0",
"vue": "^3.2.6"
"ace-builds": "^1.14.0",
"echarts": "^5.4.1",
"element-plus": "^2.2.9",
"pinia": "^2.0.16",
"vue": "^3.2.25",
"vue-echarts": "^6.5.1",
"vue-router": "4",
"vue3-ace-editor": "^2.2.2"
},
"devDependencies": {
"@vicons/fluent": "^0.11.0",
"@vitejs/plugin-vue": "^1.6.1",
"@vue/compiler-sfc": "^3.2.6",
"naive-ui": "^2.18.2",
"typescript": "^4.3.2",
"vite": "^2.5.4",
"echarts": "^5.2.1",
"vue-echarts": "^6.0.0",
"vue-tsc": "^0.2.2"
"@commitlint/cli": "^17.0.3",
"@commitlint/config-conventional": "^17.0.3",
"@typescript-eslint/eslint-plugin": "^5.30.5",
"@typescript-eslint/parser": "^5.30.5",
"@vitejs/plugin-vue": "^2.3.3",
"eslint": "^8.19.0",
"eslint-config-prettier": "^8.5.0",
"eslint-define-config": "^1.5.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.2.0",
"husky": "^8.0.1",
"less": "^4.1.3",
"prettier": "^2.7.1",
"typescript": "^4.5.4",
"vite": "^2.9.9",
"vite-plugin-eslint": "^1.6.1",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-windicss": "^1.8.6",
"vue-eslint-parser": "^9.0.3",
"vue-tsc": "^0.34.7",
"windicss": "^3.5.6"
},
"lint-staged": {
"*.{js,jsx,vue,ts,tsx}": [
"pnpm run lint:eslint"
]
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,20 @@
module.exports = {
printWidth: 100, // 最大行长规则通常设置为 100 或 120
tabWidth: 2, // 指定每个标签缩进级别的空格数。
useTabs: false, // 使用制表符而不是空格缩进行。
semi: true, // true默认: 在每条语句的末尾添加一个分号。false仅在可能导致 ASI 失败的行的开头添加分号。
vueIndentScriptAndStyle: true, // Vue 文件脚本和样式标签缩进
singleQuote: true, // 使用单引号而不是双引号
quoteProps: 'as-needed', // 引用对象中的属性时,仅在需要时在对象属性周围添加引号。
bracketSpacing: true, // 在对象文字中的括号之间打印空格。
trailingComma: 'none', // "none":没有尾随逗号。"es5": 在 ES5 中有效的尾随逗号对象、数组等TypeScript 中的类型参数中没有尾随逗号。"all"- 尽可能使用尾随逗号。
bracketSameLine: false, // 将>多行 HTMLHTML、JSX、Vue、Angular元素放在最后一行的末尾而不是单独放在下一行不适用于自闭合元素
jsxSingleQuote: false, // 在 JSX 中使用单引号而不是双引号。
arrowParens: 'always', // 在唯一的箭头函数参数周围始终包含括号。
insertPragma: false, // 插入编译指示
requirePragma: false, // 需要编译指示
proseWrap: 'never', // 如果散文超过打印宽度,则换行
htmlWhitespaceSensitivity: 'strict', // 所有标签周围的空格(或缺少空格)被认为是重要的。
endOfLine: 'auto', // 确保在文本文件中仅使用 ( \n)换行,常见于 Linux 和 macOS 以及 git repos 内部。
rangeStart: 0 // 格式化文件时,回到包含所选语句的第一行的开头。
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

@ -1,159 +1,9 @@
<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
import SvgEditor from './components/SvgEditor.vue';
import SvgPrview from './components/SvgPrview.vue';
import axios from "axios";
import { ref } from '@vue/reactivity';
const interval = ref<any>(null);
const component_infos = ref([]);
const ref_svgedit = ref();
//
const prview_data = ref<any>([]);
//
axios.get("InterfaceReturn.json").then(res => {
component_infos.value = res.data;
});
// 0 1
const displaymode = ref(0);
//svg
const savesvg_data = ref([]);
//svgdom
const savesvg_dom_data: any = ref(null);
//
const saveSvgInfo = (svg_data: any, svg_dom: any) => {
savesvg_data.value = svg_data;
savesvg_dom_data.value = svg_dom;
}
const downloadSvgData = () => {
if (savesvg_data.value == null) {
alert('请先保存绘制数据');
return;
}
let datastr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(savesvg_data.value))
let download = document.createElement('a')
download.setAttribute('href', datastr)
download.setAttribute('download', `${new Date().getTime()}.json`)
download.click()
download.remove();
}
const downloadSvgDomData = () => {
if (savesvg_dom_data.value == null) {
alert('请先保存绘制数据');
return;
}
console.log(savesvg_dom_data.value);
let datastr = 'data:text;charset=utf-8,' + encodeURIComponent(savesvg_dom_data.value.outerHTML);
let download = document.createElement('a')
download.setAttribute('href', datastr)
download.setAttribute('download', `${new Date().getTime()}.html`)
download.click()
download.remove()
}
const switchMode = () => {
displaymode.value = displaymode.value == 0 ? 1 : 0;
if (displaymode.value == 1) {
prview_data.value = [];
setTimeout(() => {
prview_data.value = savesvg_data.value;
}, 500);
}
}
const loadExampleData = () => {
axios.get("example.json").then(res => {
clearInterval(interval.value)
prview_data.value = res.data;
let temp: Array<any> = [...res.data];
//
//
let anyCircuitBreakerList = temp.filter(f => f.type == 'CircuitBreakerSvg');
//线
const anyWireBreakList = temp.filter(f => f.type == 'WireBreakSvg');
//
const anyEchartsPieList = temp.filter(f => f.type == 'EchartsPieSvg');
//
const anyEchartsBasicBarSvgList = temp.filter(f => f.type == 'EchartsBasicBarSvg');
interval.value = setInterval(function () {
anyCircuitBreakerList.forEach(anyCircuitBreaker => {
//
let random = Math.round(Math.random() * 10);
if (random < 5) {
(anyCircuitBreaker as any).extend_attr.switch.val.selectval = '{\"fill\":\"#FF0000\"}';
}
else {
(anyCircuitBreaker as any).extend_attr.switch.val.selectval = '{\"fill\":\"#00FF00\"}';
}
});
anyWireBreakList.forEach(anyWireBreak => {
//
let random = Math.round(Math.random() * 10);
if (random < 5) {
(anyWireBreak as any).extend_attr.switch.val.selectval = '{\"x2\":-10}';
}
else {
(anyWireBreak as any).extend_attr.switch.val.selectval = '{\"x2\":0}';
}
});
anyEchartsPieList.forEach(anyEchartsPie => {
const temp_val = JSON.parse(anyEchartsPie.extend_attr.echarts_option.val);
(temp_val.series[0].data as Array<any>).forEach(f => {
//
let random = Math.round(Math.random() * 100);
f.value = random;
});
anyEchartsPie.extend_attr.echarts_option.val = JSON.stringify(temp_val);
})
anyEchartsBasicBarSvgList.forEach(anyEchartsBasicBar => {
let data_arr = [Math.round(Math.random() * 300), Math.round(Math.random() * 300), Math.round(Math.random() * 300), Math.round(Math.random() * 300), Math.round(Math.random() * 300), Math.round(Math.random() * 300), Math.round(Math.random() * 300)];
const temp_val = JSON.parse(anyEchartsBasicBar.extend_attr.echarts_option.val);
temp_val.series[0].data = data_arr;
anyEchartsBasicBar.extend_attr.echarts_option.val = JSON.stringify(temp_val);
})
const tempa = JSON.stringify(temp)
prview_data.value = JSON.parse(tempa);
}, 2000)
});
}
const loadExample = (file_name: string) => {
axios.get(file_name).then(res => {
console.log(ref_svgedit.value);
(ref_svgedit.value as any).setSvgLists(res.data);
})
}
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import 'element-plus/dist/index.css';
</script>
<template>
<div style="text-align: center;">
<span>此处为测试演示用 组件不包含此元素</span>
<button class="btn" @click="loadExample('example.json')" v-if="displaymode == 0"></button>
<button class="btn" @click="loadExample('example2.json')" v-if="displaymode == 0"></button>
<button class="btn" @click="switchMode">{{ displaymode == 0 ? '' : '' }}</button>
<button class="btn" @click="downloadSvgData" v-if="displaymode == 0"></button>
<button class="btn" @click="downloadSvgDomData" v-if="displaymode == 0">svg</button>
<button class="btn" @click="loadExampleData" v-if="displaymode == 1"></button>
</div>
<div style="width:1500px;height:650px;margin:0 auto">
<div v-show="displaymode == 0">
<svg-editor
ref="ref_svgedit"
:component_infos="component_infos"
@saveSvgInfo="saveSvgInfo"
:svgCanvas="{ width: 1920, height: 1080 }"
></svg-editor>
</div>
<div v-show="displaymode != 0">
<svg-prview
:component_infos="component_infos"
:svg_data="prview_data"
:svgCanvas="{ width: 1920, height: 1080 }"
></svg-prview>
</div>
</div>
<router-view></router-view>
</template>
<style>
.btn {
margin-left: 1rem;
}
</style>

@ -1,77 +0,0 @@
/**
* @description:
* @param {*} commonComponentList
* @param {*} drawComponentList
* @param {*} chartComponentList
* @return {*}
*/
export interface ILeftImgLists {
commonComponentList?: Array<IComponentInfo>,
drawComponentList?: Array<IComponentInfo>,
chartComponentList?: Array<IComponentInfo>
}
/**
* @description:
* @param {*}
* @return {*}
*/
export interface ISvgDataLists {
id: string,
type?: string,
title?: string,
svgPositionX: number,
svgPositionY: number,
angle?: number,
size?: number,
extend_attr?: any
}
export interface ISvgCanvas {
width: number,
height: number
}
/**
* @description:
* @param {*} type
* @param {*} title
* @param {*} panel_class
* @param {*} template
* @param {*} props
* @param {*} extend_attr
* @param {*} create_type
* @param {*} priview_img
* @return {*}
*/
export interface IComponentInfo {
type?: string,
title?: string,
panel_class?: string,
template?: string,//
props?: Array<string>,
extend_attr?: any,
create_type?: string,
priview_img?: string
}
/**
* @description:
* @param {*} status 1 0
* @param {*} mPositionX x
* @param {*} mPositionY y
* @return {*}
*/
export interface IMouseInfo {
status: number,
mPositionX: number
mPositionY: number
}
/**
* @description: svg
* @param {*}
* @return {*}
*/
export interface ISelectSvg {
id: string,
index: number,
sPositionX: number,
sPositionY: number,
create_type: string,
}

@ -1,111 +0,0 @@
/* 正向流动效果 */
.svg_ani_flow {
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
animation: ani_flow 10s linear infinite;
animation-fill-mode: forwards;
-webkit-animation: ani_flow 10s linear infinite;
-webkit-animation-fill-mode: forwards;
}
@keyframes ani_flow {
from {
stroke-dasharray: 10, 5;
}
to {
stroke-dasharray: 13, 5;
}
}
@-webkit-keyframes ani_flow {
from {
stroke-dasharray: 10, 5;
}
to {
stroke-dasharray: 13, 5;
}
}
/* 停止流动效果 */
.svg_ani_flow_stop {
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
animation: ani_flow_stop 10s linear infinite;
animation-fill-mode: forwards;
-webkit-animation: ani_flow_stop 10s linear infinite;
-webkit-animation-fill-mode: forwards;
}
@keyframes ani_flow_stop {
from {
stroke-dasharray: 10, 5;
}
to {
stroke-dasharray: 10, 5;
}
}
@-webkit-keyframes ani_flow_stop {
from {
stroke-dasharray: 10, 5;
}
to {
stroke-dasharray: 10, 5;
}
}
/* 反向流动效果 */
.svg_ani_flow_back {
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
animation: ani_flow_back 10s linear infinite;
animation-fill-mode: forwards;
-webkit-animation: ani_flow_back 10s linear infinite;
-webkit-animation-fill-mode: forwards;
}
@keyframes ani_flow_back {
from {
stroke-dasharray: 13, 5;
}
to {
stroke-dasharray: 10, 5;
}
}
@-webkit-keyframes ani_flow_stop {
from {
stroke-dasharray: 10, 5;
}
to {
stroke-dasharray: 10, 5;
}
}
/* 以最大40高度填充 */
.svg_ani_fill_h40 {
animation: ani_fill_h40 5s linear infinite;
animation-fill-mode: forwards;
-webkit-animation: ani_fill_h40 5s linear infinite;
-webkit-animation-fill-mode: forwards;
}
@keyframes ani_fill_h40 {
from {
height: 0px;
}
to {
height: 40px;
}
}
@-webkit-keyframes ani_flow_stop {
from {
stroke-dasharray: 10, 5;
}
to {
stroke-dasharray: 10, 5;
}
}

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
<g stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 10V44H39V10H9Z" fill="none" />
<path d="M20 20V33" />
<path d="M28 20V33" />
<path d="M4 10H44" />
<path d="M16 10L19.289 4H28.7771L32 10H16Z" fill="none" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 451 B

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1024 1024">
<path d="M888.3 757.4h-53.8c-4.2 0-7.7 3.5-7.7 7.7v61.8H197.1V197.1h629.8v61.8c0 4.2 3.5 7.7 7.7 7.7h53.8c4.2 0 7.7-3.4 7.7-7.7V158.7c0-17-13.7-30.7-30.7-30.7H158.7c-17 0-30.7 13.7-30.7 30.7v706.6c0 17 13.7 30.7 30.7 30.7h706.6c17 0 30.7-13.7 30.7-30.7V765.1c0-4.3-3.5-7.7-7.7-7.7zm18.6-251.7L765 393.7c-5.3-4.2-13-.4-13 6.3v76H438c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h314v76c0 6.7 7.8 10.5 13 6.3l141.9-112a8 8 0 0 0 0-12.6z" fill="currentColor"></path>
</svg>

After

Width:  |  Height:  |  Size: 576 B

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M12 4H4V12H12V4Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
<path d="M44 36H36V44H44V36Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
<path d="M12 36H4V44H12V36Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
<path d="M44 4H36V12H44V4Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
<path d="M8 36V12" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M40 36V12" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 8H36" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 40H36" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 16H25.6V22.4H32V32H22.4V25.6H16V16Z" fill="none" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1024 1024">
<path d="M888.3 757.4h-53.8c-4.2 0-7.7 3.5-7.7 7.7v61.8H197.1V197.1h629.8v61.8c0 4.2 3.5 7.7 7.7 7.7h53.8c4.2 0 7.7-3.4 7.7-7.7V158.7c0-17-13.7-30.7-30.7-30.7H158.7c-17 0-30.7 13.7-30.7 30.7v706.6c0 17 13.7 30.7 30.7 30.7h706.6c17 0 30.7-13.7 30.7-30.7V765.1c0-4.3-3.5-7.7-7.7-7.7zM902 476H588v-76c0-6.7-7.8-10.5-13-6.3l-141.9 112a8 8 0 0 0 0 12.6l141.9 112c5.3 4.2 13 .4 13-6.3v-76h314c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z" fill="currentColor"></path>
</svg>

After

Width:  |  Height:  |  Size: 575 B

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
<rect x="6" y="22" width="36" height="22" rx="2" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
<path d="M14 22V14C14 8.47715 18.4772 4 24 4C29.5228 4 34 8.47715 34 14V22" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M24 30V36" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 531 B

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M8 10.5H40" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M24 19.5H40" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M24 28.5H40" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 37.5H40" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 19L16 24L8 29V19Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 671 B

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M8 10.5H40" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M24 19.5H40" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M24 28.5H40" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 37.5H40" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 19L8 24L16 29V19Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 672 B

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M24 36C35.0457 36 44 24 44 24C44 24 35.0457 12 24 12C12.9543 12 4 24 4 24C4 24 12.9543 36 24 36Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
<path d="M24 29C26.7614 29 29 26.7614 29 24C29 21.2386 26.7614 19 24 19C21.2386 19 19 21.2386 19 24C19 26.7614 21.2386 29 24 29Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 528 B

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
<g stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round">
<path d="M36.728 36.728A17.943 17.943 0 0 1 24 42c-9.941 0-18-8.059-18-18S14.059 6 24 6c4.97 0 9.47 2.015 12.728 5.272C38.386 12.93 42 17 42 17"></path>
<path d="M42 8v9h-9"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 438 B

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.9998 8L6 14L12.9998 21" stroke="#333" stroke-width="4" stroke-linecap="round"
stroke-linejoin="round" />
<path
d="M6 14H28.9938C35.8768 14 41.7221 19.6204 41.9904 26.5C42.2739 33.7696 36.2671 40 28.9938 40H11.9984"
stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" />
</svg>

After

Width:  |  Height:  |  Size: 482 B

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M44 24C44 12.9543 35.0457 4 24 4C12.9543 4 4 12.9543 4 24C4 35.0457 12.9543 44 24 44C30.9566 44 37.0836 40.4483 40.6667 35.0593" stroke="#333" stroke-width="4" stroke-linecap="round"/><path d="M44 24H30" stroke="#333" stroke-width="4" stroke-linecap="round"/><circle cx="24" cy="24" r="6" fill="#333" stroke="#333" stroke-width="4"/></svg>

After

Width:  |  Height:  |  Size: 481 B

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<g stroke="#ffffff" stroke-width="4">
<path d="M39.3 6.00012H8.7C7.20883 6.00012 6 7.20895 6 8.70012V39.3001C6 40.7913 7.20883 42.0001 8.7 42.0001H39.3C40.7912 42.0001 42 40.7913 42 39.3001V8.70012C42 7.20895 40.7912 6.00012 39.3 6.00012Z" fill="none" stroke-linejoin="round" />
<path d="M32 6V24H15V6H32Z" fill="none" stroke-linejoin="round" />
<path d="M26 13.0001V17.0001" stroke-linecap="round" />
<path d="M10.9969 6.00012H35.9985" stroke-linecap="round" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 653 B

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M38 20H18V28H38V20Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
<path d="M32 6H18V14H32V6Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
<path d="M44 34H18V42H44V34Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
<path d="M17 10H5" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17 24H5" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17 38H5" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 44V4" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 863 B

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<g stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round">
<path d="M11.2721 36.7279C14.5294 39.9853 19.0294 42 24 42C33.9411 42 42 33.9411 42 24C42 14.0589 33.9411 6 24 6C19.0294 6 14.5294 8.01472 11.2721 11.2721C9.61407 12.9301 6 17 6 17"/>
<path d="M6 9V17H14" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 460 B

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M11.2727 4H4V11.2727H11.2727V4Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
<path d="M43.9998 36.7271H36.7271V43.9998H43.9998V36.7271Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
<path d="M11.2727 24H4V31.2727H11.2727V24Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
<path d="M23.9998 36.7271H16.7271V43.9998H23.9998V36.7271Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
<path d="M31.2727 4H24V11.2727H31.2727V4Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
<path d="M43.9998 16.7271H36.7271V23.9998H43.9998V16.7271Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
<path d="M11.2729 7.63623H24.0002" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M24 40.3638H36.7273" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.2729 27.6366H27.6366V11.2729" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M28.8275 20.3633H36.7269M20.3633 36.7269V27.6282V36.7269Z" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.63672 11.2725V23.9997" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M40.3633 24V36.7273" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" viewBox="0 0 48 48" fill="none"
xmlns="http://www.w3.org/2000/svg">
<rect x="7" y="22.0476" width="34" height="22" rx="2" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"/>
<path d="M14 22V14.0047C13.9948 8.87022 17.9227 4.56718 23.0859 4.05117C28.249 3.53516 32.9673 6.97408 34 12.0059" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M24 30V36" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 575 B

@ -0,0 +1,8 @@
<?xml version="1.0" standalone="no"?>
<svg viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
<path d="M512 273.194667A238.805333 238.805333 0 1 1 273.194667 512 238.805333 238.805333 0 0 1 512 273.194667M512 234.666667a277.333333 277.333333 0 1 0 277.333333 277.333333 277.333333 277.333333 0 0 0-277.333333-277.333333z"></path>
<path d="M675.157333 556.928a14.677333 14.677333 0 0 0-12.8 7.445333 96 96 0 0 1-137.109333 24.661334 125.098667 125.098667 0 0 0-188.032 24.149333 14.208 14.208 0 0 0-1.28 2.133333 5.802667 5.802667 0 0 0-0.661333 1.408 14.656 14.656 0 0 0 24.149333 15.68 6.272 6.272 0 0 0 1.28-1.493333 96.832 96.832 0 0 1 13.248-16.490667 95.765333 95.765333 0 0 1 124.629333-9.301333 125.077333 125.077333 0 0 0 189.269334-26.176 5.824 5.824 0 0 0 0.682666-1.493333 14.634667 14.634667 0 0 0-13.44-20.437334z"></path>
<path d="M593.834667 444.778667l-12.8 1.557333c-0.192 13.674667-0.384 27.733333-0.384 43.2v9.621333a89.6 89.6 0 0 1-47.296 12.8 76.16 76.16 0 0 1 1.173333-152.277333 81.066667 81.066667 0 0 1 42.666667 10.666667l-0.405334 33.813333h-11.648l-8.213333-32.426667a41.088 41.088 0 0 0-18.965333-4.117333c-29.333333 0-50.837333 23.104-50.837334 68.053333 0 42.666667 20.714667 68.266667 49.642667 68.266667a65.088 65.088 0 0 0 17.792-2.304v-12.8c0-14.272-0.192-27.946667-0.597333-42.026667l-21.461334-2.133333v-6.848h61.376z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -0,0 +1,11 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200"
height="200">
<path
d="M600.7,404.1v-16.7c0-13-5.5-25.9-16.4-33.3l-54.8-44.5c-5.5-5.6-9.1-13-9.1-20.4v-66.7c0-13-11-24.1-23.7-24.1
H260.9c-12.8,0-23.7,11.1-23.7,24.1v607.5c0,13,11,24.1,23.7,24.1h235.7c12.8,0,23.7-11.1,23.7-24.1v-35.2c0-14.8,11-25.9,25.6-25.9
h12.8c23.7,0,43.8-20.4,43.8-44.5V656c62.1-7.4,111.4-61.1,111.4-125.9c0-29.6-9.1-57.4-27.4-77.8
C675.4,430,626.2,407.8,600.7,404.1z M576.9,726.4h-1.8c0,9.3-7.3,18.5-18.3,18.5H544c-27.4,0-51.2,24.1-51.2,51.9v33.3H262.7V378.2
h124.2v231.5c0,7.4,5.5,13,12.8,13s12.8-5.6,12.8-13V365.2c0-7.4-5.5-13-12.8-13h-137V224.4h232v64.8c0,16.7,7.3,31.5,20.1,40.7
l54.8,44.5c3.7,3.7,7.3,9.3,7.3,13V726.4z M600.7,630.1V430c49.3,5.6,85.9,48.2,85.9,100C686.5,581.9,648.2,624.5,600.7,630.1z" fill-opacity="1"/>
<polygon points="854.5,400.6 693.6,461.2 703,490.5 863.9,430"/>
</svg>

After

Width:  |  Height:  |  Size: 970 B

@ -0,0 +1,5 @@
<svg viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
<text x="250" y="700" font-family="Microsoft YaHei" font-size="505" ></text>
</svg>

After

Width:  |  Height:  |  Size: 244 B

@ -0,0 +1,7 @@
<svg viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="200" height="200">
<path
d="M746.666667 213.333333h-469.333334C243.2 213.333333 213.333333 243.2 213.333333 277.333333v469.333334c0 34.133333 29.866667 64 64 64h469.333334c34.133333 0 64-29.866667 64-64v-469.333334c0-34.133333-29.866667-64-64-64zM256 439.466667h140.8v140.8H256v-140.8z m21.333333 328.533333c-12.8 0-21.333333-8.533333-21.333333-21.333333v-119.466667h140.8V768H277.333333z m307.2 0h-140.8v-140.8h140.8V768z m0-183.466667h-140.8v-140.8h140.8v140.8z m183.466667 162.133334c0 12.8-8.533333 21.333333-21.333333 21.333333h-119.466667v-140.8H768v119.466667z m0-162.133334h-140.8v-140.8H768v140.8z m0-187.733333H256V277.333333c0-12.8 8.533333-21.333333 21.333333-21.333333h469.333334c12.8 0 21.333333 8.533333 21.333333 21.333333v119.466667z"
fill="#1296db"></path>
</svg>

After

Width:  |  Height:  |  Size: 937 B

@ -0,0 +1,10 @@
<svg viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="3587" xmlns:xlink="http://www.w3.org/1999/xlink"
width="200" height="200">
<path
d="M883.3 745H142.7c-41.9 0-76-34.1-76-76V351.6c0-41.9 34.1-76 76-76h740.6c41.9 0 76 34.1 76 76V669c0 41.9-34.1 76-76 76zM142.7 321.8c-16.4 0-29.8 13.3-29.8 29.8V669c0 16.4 13.3 29.8 29.8 29.8h740.6c16.4 0 29.8-13.3 29.8-29.8V351.6c0-16.4-13.3-29.8-29.8-29.8H142.7z"
fill="#7D7D7D"></path>
<path
d="M191.3 576c-10.1 0-13.9 0.1-19.6 0.4 0.8-5.7 1.1-10.2 1.1-19.4v-72.4c0-7-0.3-12.7-1.1-19 6.7 0.4 8.6 0.4 19.4 0.4h34.7c21.4 0 34 10.3 34 27.8 0 8.1-2.6 14.4-7.7 18.9-2.9 2.5-5.3 3.8-10.7 5.8 6.5 1.5 9.9 3 13.8 6.4 5.7 5 8.6 12.2 8.6 20.9 0 19-13.5 30.3-36.5 30.3h-36z m30.6-67.6c7 0 11.4-4.1 11.4-10.6s-4.1-10.2-11.6-10.2h-23v20.8h23.2z m-23.2 45.8H223c8.3 0 13.4-4.6 13.4-12.4 0-7.7-5-12.2-13.5-12.2h-24.2v24.6z m186.2-88.3c-0.8 5.7-1.1 10.4-1.1 20v45.5c0 30.2-18.1 47.2-50 47.2-16.4 0-29.8-4.6-37.7-13-7.7-8.2-11.6-19.7-11.6-34.5v-45.2c0-9.1-0.3-14.9-1.1-20H312c-0.8 4.9-1.1 10.2-1.1 20v45.5c0 16.7 7.5 24.6 23 24.6 15.9 0 23.4-7.9 23.4-24.6v-45.5c0-9.9-0.3-14-1.1-20h28.7z m79.2 90.1c0 7.9 0.3 13.8 1.1 20H436c0.8-6.2 1.1-11.6 1.1-20v-66.7h-15.6c-8.3 0-10.6 0.1-20 0.9V465c4.4 0.5 11.6 0.9 19.7 0.9H479c9.3 0 14.5-0.3 20.6-0.9v25.3c-6-0.7-11.1-0.9-20.6-0.9h-14.9V556z m108.8 0c0 7.9 0.3 13.8 1.1 20h-29.2c0.8-6.2 1.1-11.6 1.1-20v-66.7h-15.6c-8.3 0-10.6 0.1-20 0.9V465c4.4 0.5 11.6 0.9 19.7 0.9h57.9c9.3 0 14.5-0.3 20.6-0.9v25.3c-6-0.7-11.1-0.9-20.6-0.9H573V556z m155.8-35.6c0 34.9-21.3 58.1-53.4 58.1-32.4 0-53.3-22.6-53.3-57.7 0-34.9 20.9-57.3 53.4-57.3 32.6 0 53.3 22.3 53.3 56.9z m-27.5 0.3c0-21.4-9.8-34.4-25.8-34.4-16.1 0-26.1 13.1-26.1 34.4 0 21.6 9.9 34.9 26.1 34.9 16 0 25.8-13.2 25.8-34.9z m110.6-0.7c3.8 5.7 6.3 9.9 9.5 15.7-0.5-6.1-0.8-12.2-0.8-19.6V486c0-8.7-0.3-14-1.1-20.1h28.2c-0.8 6-1.1 11.5-1.1 20.1v70.2c0 8.1 0.4 14.2 1.1 19.7h-29c-2.4-5-5.4-9.9-9.9-16.8l-24.5-36.9c-3.6-5.3-5.8-9.3-9.5-16.3 0.7 6 0.9 13.2 0.9 19.7v29.6c0 9.1 0.3 14.9 1.1 20.6h-28.2c0.8-5.2 1.1-11 1.1-20.8v-69.6c0-7.7-0.3-13.6-1.1-19.7h28.7c1.3 3.6 4.2 8.9 9.4 16.5l25.2 37.8z"
fill="#7D7D7D"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@ -0,0 +1,13 @@
<svg viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="4650" xmlns:xlink="http://www.w3.org/1999/xlink"
width="200" height="200">
<path
d="M767.1 292.6c-0.2-19-15.6-34.4-34.6-34.6l-202.2-2.5c-9.5-0.1-18.6 3.6-25.2 10.3l-35.4 35.4c48.4 138.5 72.4 282.3 42.1 433.8-1.7 10.4-3.9 21.1-6.4 31.9 6-1.5 11.7-4.4 16.3-9.1l237.6-237.6c6.7-6.7 10.4-15.8 10.3-25.2l-2.5-202.4zM664.3 429c-17.1 17.1-44.9 17.1-62 0s-17.1-44.9 0-62 44.9-17.1 62 0 17.2 44.8 0 62z"
fill="#FFDE55"></path>
<path
d="M779.6 292.5c-0.2-12.5-5.1-24.2-13.9-33.1-8.8-8.8-20.6-13.8-33.1-13.9L530.4 243c-12.7-0.1-25.2 4.9-34.2 13.9L258.6 494.6c-9 9-13.9 20.9-13.9 33.6 0 12.7 4.9 24.7 13.9 33.6l204.7 204.7c9 9 20.9 13.9 33.6 13.9 12.7 0 24.7-4.9 33.6-13.9l237.6-237.6c9-9 14.1-21.5 13.9-34.2l-2.4-202.2z m-29.1 218.7L512.9 748.8c-4.3 4.3-9.9 6.6-16 6.6-6 0-11.7-2.3-16-6.6L276.3 544.2c-4.3-4.3-6.6-9.9-6.6-16s2.3-11.7 6.6-16l237.6-237.6c4.2-4.2 10-6.6 16-6.6h0.3l202.2 2.5c12.4 0.2 22.2 9.9 22.3 22.3l2.5 202.2c0 6-2.4 11.9-6.7 16.2z"
fill=""></path>
<path
d="M593.5 358.1c-22 22-22 57.7 0 79.7 10.6 10.6 24.8 16.5 39.8 16.5s29.2-5.9 39.8-16.5c22-22 22-57.7 0-79.7-21.9-22-57.6-22-79.6 0z m62 62c-5.9 5.9-13.8 9.2-22.2 9.2s-16.2-3.3-22.2-9.2c-12.2-12.2-12.2-32.1 0-44.3 6.1-6.1 14.1-9.2 22.2-9.2 8 0 16.1 3.1 22.2 9.2 12.2 12.2 12.2 32.1 0 44.3z"
fill=""></path>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -0,0 +1,10 @@
<svg t="1673874136249" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="200" height="200">
<path
d="M538.4 504.4l280.9 144.3c9.9 5.1 22.2 0.5 26-10 14.3-38.7 22.1-80.6 22.1-124.3 0-186.2-141.6-339.3-323-357.5-7.9-0.8-14.7 5.3-14.7 13.3v320.1c0 5.9 3.4 11.4 8.7 14.1z"
fill="#64B5F6"></path>
<path
d="M492.5 514.5V172.3c0-9.3-8-16.6-17.3-15.8C289 173.4 143.7 332.3 148.7 524.1c5 190.9 161.5 346.1 352.4 349.7 131.3 2.5 246.8-65.5 311.5-168.7 4.6-7.3 2-17-5.7-21L499.8 526.4c-4.5-2.3-7.3-6.9-7.3-11.9z"
fill="#1E88E5"></path>
</svg>

After

Width:  |  Height:  |  Size: 665 B

@ -0,0 +1,10 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200"
height="200">
<rect transform="rotate(180,427,410)" x="0" y="0" width="680" height="400" stroke="none">
<animate attributeName="height" from="0" to="400" dur="5s" repeatCount="indefinite" />
</rect>
<polygon
points="283.2,131 87.7,129.1 85.1,911.2 938.9,911.2 938.9,131 740.8,131 740.8,241.3 849,245.4 853.1,807.1
177.1,799.2 181.1,246.6 283.2,245 "
stroke-width="10" />
</svg>

After

Width:  |  Height:  |  Size: 478 B

@ -0,0 +1,11 @@
<?xml version="1.0" standalone="no"?>
<svg viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
<path d="M500.181333 308.757333a118.421333 118.421333 0 1 1-118.421333 118.421334 118.421333 118.421333 0 0 1 118.421333-118.421334m0-19.2a137.514667 137.514667 0 1 0 137.514667 137.514667 137.514667 137.514667 0 0 0-137.514667-137.514667z"></path>
<path d="M500.181333 478.741333a118.421333 118.421333 0 1 1-118.421333 118.421334 118.421333 118.421333 0 0 1 118.421333-118.421334m0-19.2a137.514667 137.514667 0 1 0 137.514667 137.514667 137.514667 137.514667 0 0 0-137.514667-137.514667z"></path>
<path d="M500.266667 234.666667a9.557333 9.557333 0 0 1 9.557333 9.557333v54.869333a9.557333 9.557333 0 0 1-9.557333 9.557334 9.557333 9.557333 0 0 1-9.557334-9.557334v-54.848A9.557333 9.557333 0 0 1 500.266667 234.666667z"></path>
<path d="M500.266667 715.349333a9.557333 9.557333 0 0 1 9.557333 9.557334v54.890666a9.557333 9.557333 0 0 1-9.557333 9.557334 9.557333 9.557333 0 0 1-9.557334-9.557334v-54.869333a9.557333 9.557333 0 0 1 9.557334-9.578667z"></path>
<path d="M500.266667 446.357333a10.666667 10.666667 0 0 1-10.218667-6.186666l-36.053333-77.312a10.666667 10.666667 0 0 1 5.205333-14.186667 10.666667 10.666667 0 0 1 14.101333 5.205333l26.986667 57.728 26.922667-57.728a10.666667 10.666667 0 0 1 14.186666-5.205333 10.666667 10.666667 0 0 1 5.12 14.186667l-36.053333 77.312a10.666667 10.666667 0 0 1-9.664 6.208z"></path>
<path d="M500.266667 681.024a10.666667 10.666667 0 0 1-10.218667-6.186667l-36.053333-77.312a10.666667 10.666667 0 0 1 5.205333-14.186666 10.666667 10.666667 0 0 1 14.101333 5.205333l26.986667 57.728 26.922667-57.728a10.666667 10.666667 0 0 1 14.186666-5.205333 10.666667 10.666667 0 0 1 5.12 14.186666l-36.053333 77.312a10.666667 10.666667 0 0 1-9.664 6.208z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@ -1,20 +0,0 @@
<template>
<n-card title="操作说明" embedded :bordered="false">
<ul>
<li>常规组件和图表组件用鼠标左键选中并按住可拖动至画布</li>
<li>绘制组件需先选中 再将鼠标移至画布中按住左键开始绘制 松开左键结束绘制</li>
<li>在画布上单击左键并按住可以拖动组件改变位置</li>
<li>当组件选中时会有选中效果 此时右侧面板弹出 可使用快捷键和编辑右侧面板属性更改组件</li>
<li>鼠标右键点击组件可以进行一些快捷操作</li>
<li>键盘可移动选中组件 ctrl+c复制当前选中组件 deleted删除当前选中组件</li>
<li>ctrl+键盘可移动组件图层</li>
<li>点击画布空白处可以取消选中组件 并关闭属性面板</li>
<li>点击 符号可以显示或隐藏左侧面板</li>
<li>如果使用我的在线体验地址 请先保存绘制再切换到预览界面</li>
<li>更多使用帮助请参考<a href="https://github.com/yaolunmao/vue-webtopo-svgeditor">github</a><a href="https://gitee.com/yaolunmao/vue-webtopo-svgeditor">gitee</a></li>
</ul>
</n-card>
</template>
<script setup lang="ts">
import { NCard } from "naive-ui";
</script>

@ -1,155 +0,0 @@
<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue';
import { ChevronDoubleLeft20Regular, ChevronDoubleRight20Regular } from '@vicons/fluent';
import { NIcon, NCollapse, NCollapseItem, useMessage } from "naive-ui";
import { ILeftImgLists, IComponentInfo } from "../Model";
const emit = defineEmits(['setCreatSvgInfo'])
const message = useMessage();
const props = defineProps({
left_imglists: {
type: Object as () => ILeftImgLists,
default: []
},
//
select_toolbar: {
type: String,
default: ''
}
});
const leftnav = reactive({
navclass: 'leftnavDisplay',
navopen: true
});
const clickHandleIcon = () => {
leftnav.navopen = !leftnav.navopen;
leftnav.navclass = leftnav.navclass == 'leftnavDisplay' ? 'leftnavNone' : 'leftnavDisplay'
};
const dragStartEvent = (leftImgItem: IComponentInfo, e: DragEvent) => {
//svg
emit('setCreatSvgInfo', leftImgItem);
}
const dragEndEvent = (leftImgItem: IComponentInfo, e: DragEvent) => {
//svg
if (e.dataTransfer?.dropEffect !== 'copy') {
message.error('请将组件拖到画布中!');
//
emit('setCreatSvgInfo', {});
return;
}
}
</script>
<template>
<div :class="leftnav.navclass">
<div class="svgimg" v-show="leftnav.navopen">
<n-collapse default-expanded-names="1" accordion>
<n-collapse-item title="常规组件" name="1" style="margin-top: 15px;">
<ul class="leftImgUl">
<li v-for="leftImgItem in props.left_imglists.commonComponentList">
<img
:title="leftImgItem.title"
:src="leftImgItem.priview_img"
draggable="true"
@dragstart="dragStartEvent(leftImgItem, $event)"
@dragend="dragEndEvent(leftImgItem, $event)"
/>
</li>
</ul>
</n-collapse-item>
<n-collapse-item title="绘制组件" name="2">
<ul class="leftImgUl">
<li v-for="leftImgItem in props.left_imglists.drawComponentList">
<img
:class="props.select_toolbar == leftImgItem.type ? 'svg-selected' : ''"
:title="leftImgItem.title"
:src="leftImgItem.priview_img"
@click="() => { emit('setCreatSvgInfo', leftImgItem) }"
/>
</li>
</ul>
</n-collapse-item>
<n-collapse-item title="图表组件" name="3">
<ul class="leftImgUl">
<li v-for="leftImgItem in props.left_imglists.chartComponentList">
<img
:title="leftImgItem.title"
:src="leftImgItem.priview_img"
draggable="true"
@dragstart="dragStartEvent(leftImgItem, $event)"
@dragend="dragEndEvent(leftImgItem, $event)"
/>
</li>
</ul>
</n-collapse-item>
</n-collapse>
</div>
<div class="handlehidden">
<n-icon class="handleicon" size="10" @click="clickHandleIcon">
<chevron-double-left20-regular v-if="leftnav.navopen" />
<chevron-double-right20-regular v-else />
</n-icon>
</div>
</div>
</template>
<style scoped>
.leftnavDisplay,
.leftnavNone {
height: 100%;
display: flex;
}
.leftnavDisplay {
width: 210px;
}
.leftnavNone {
width: 10px;
}
.svgimg {
/* background-color: green; */
height: 100%;
width: 100%;
display: flex;
flex-wrap: wrap;
overflow: overlay;
}
.svgimg::-webkit-scrollbar {
display: none;
}
.handlehidden {
height: 100%;
width: 10px;
background-color: grey;
}
.handleicon {
top: 50%;
cursor: pointer;
position: relative;
}
.leftImgUl {
display: flex;
flex-wrap: wrap;
list-style: none;
margin: 0;
padding: 0;
}
.leftImgUl li {
width: calc(33.33% - 30px);
margin: 0 15px 15px 15px;
padding: 0;
border-radius: 50%;
box-shadow: 1px 1px 5px #ddd;
cursor: pointer;
align-items: center;
justify-content: center;
display: flex;
}
.leftImgUl li :hover {
box-shadow: 1px 1px 10px #ccc;
border-radius: 50%;
}
.leftImgUl img {
width: 100%;
}
.svg-selected {
outline: 1px solid #0cf;
}
</style>

@ -1,81 +0,0 @@
<script setup lang="ts">
import { reactive, ref } from "vue";
import { NForm, NFormItem, NInput, NInputNumber, NColorPicker, NRadioGroup, NSpace, NRadio,NSelect } from "naive-ui";
import { ISvgDataLists } from "../Model";
const props = defineProps({
set_svg_info: {
type: Object as () => ISvgDataLists,
default: {}
}
});
</script>
<template>
<div class="rightnav">
<div style="margin-left: 2rem;margin-top: 1rem;">
<n-form size="small" label-placement="left">
<n-form-item label="标识">
<span>{{ props.set_svg_info.id }}</span>
</n-form-item>
<n-form-item label="类型">
<span>{{ props.set_svg_info.type }}</span>
</n-form-item>
<n-form-item label="名称">
<n-input placeholder="请输入名称" v-model:value="props.set_svg_info.title" />
</n-form-item>
<n-form-item label="x轴坐标">
<n-input-number v-model:value="props.set_svg_info.svgPositionX" :min="0" />
</n-form-item>
<n-form-item label="y轴坐标">
<n-input-number v-model:value="props.set_svg_info.svgPositionY" :min="0" />
</n-form-item>
<n-form-item label="大小">
<n-input-number v-model:value="props.set_svg_info.size" :min="1" :step="0.1" />
</n-form-item>
<n-form-item label="旋转">
<n-input-number v-model:value="props.set_svg_info.angle" />
</n-form-item>
<n-form-item v-for="item in props.set_svg_info.extend_attr" :label="item.title">
<n-input-number v-if="item.type == 'numberinputbox'" v-model:value="item.val" />
<n-color-picker
v-else-if="item.type == 'colorinputbox'"
:modes="['hex']"
v-model:value="item.val"
/>
<n-input
v-else-if="item.type == 'textinputbox'"
:placeholder="`请输入${item.title}`"
v-model:value="item.val"
/>
<n-input
v-else-if="item.type == 'textareainputbox'"
:placeholder="`请输入${item.title}`"
type="textarea"
v-model:value="item.val"
/>
<n-radio-group v-model:value="item.val.selectval" v-else-if="item.type == 'radiogroup'">
<n-space>
<n-radio
v-for="ridioitem in item.val.ridiogroup"
:key="ridioitem.value"
:value="ridioitem.value"
>{{ ridioitem.label }}</n-radio>
</n-space>
</n-radio-group>
<n-select v-else-if="item.type == 'select'" v-model:value="item.val.selectval" :options="item.val.selectgroup" />
</n-form-item>
</n-form>
</div>
</div>
</template>
<style scoped>
.rightnav {
width: 250px;
height: 100%;
overflow: auto;
}
.rightnav::-webkit-scrollbar {
display: none;
}
</style>

@ -1,50 +0,0 @@
<script setup lang="ts">
import { reactive, ref, createApp, markRaw,provide } from "vue";
import { use } from "echarts/core";
import { SVGRenderer } from "echarts/renderers";
import { PieChart,BarChart } from "echarts/charts";
import {
TitleComponent,
TooltipComponent,
LegendComponent,
GridComponent
} from "echarts/components";
import { THEME_KEY } from "vue-echarts";
use([
SVGRenderer,
PieChart,
TitleComponent,
TooltipComponent,
LegendComponent,
GridComponent,
BarChart
]);
provide(THEME_KEY, "default")
const props = defineProps({
component_type: {
type: String as () => any,
default: ''
},
component_template: {
type: String as () => any,
default: ''
},
component_props: {
type: Array as () => Array<any>,
default: ''
},
component_attr: {
type: Object as () => any,
default: {}
},
});
const component_info = markRaw({
template: props.component_template,
props: ['prop_data']
});
</script>
<template>
<component :is="component_info" :prop_data="props.component_attr"></component>
</template>

@ -1,555 +0,0 @@
<script setup lang="ts">
import { computed, reactive, Ref, ref, watch } from 'vue';
import TopToolBar from './TopBar.vue';
import LeftToolBar from './LeftToolBar.vue';
import RightToolBar from './RightToolBar.vue';
import BottomBar from './BottomBar.vue';
import { NMessageProvider } from "naive-ui";
import { IComponentInfo, ISvgDataLists, ISvgCanvas, ILeftImgLists, IMouseInfo, ISelectSvg } from "../Model";
import SvgDynamic from "./SvgDynamic.vue";
import "../assets/css/svgAnimation/index.css";
import { moveUp, moveDown, moveLeft, moveRight, hotkeyCopy, hotkeyDel, hotkeyPutOnTop, hotkeyPutOnButtom, hotkeyPutOnUp, hotkeyPutOnDown } from "../func/HotkeyFunc";
const emit = defineEmits(['saveSvgInfo']);
const props = defineProps({
//json
component_infos: {
type: Array as () => Array<IComponentInfo>,
required: true,
default: []
},
svgCanvas: {
type: Object as () => ISvgCanvas,
default: { width: 1520, height: 720 }
}
});
const svg_dom_ref = ref<null | HTMLElement>(null);
const contextMenuRef = ref<HTMLElement>();
const svgLists: ISvgDataLists[] = reactive([]);
const topbar_dom_ref = ref(null);
//
const display_contextmenu = ref(false);
//
const contextmenu_data = reactive([{
name: "复制",
hotkey: "Ctrl+C",
enable: true,
fun: function () {
if (!this.enable) {
return;
}
hotkeyCopy(svgLists, select_svg);
display_contextmenu.value = false;
}
}, {
name: "删除",
hotkey: "Delete",
enable: false,
fun: function () {
if (!this.enable) {
return;
}
hotkeyDel(svgLists, select_svg);
display_contextmenu.value = false;
}
}, {
name: "置于顶层",
hotkey: "Ctrl+→",
enable: true,
fun: function () {
if (!this.enable) {
return;
}
hotkeyPutOnTop(svgLists, select_svg);
display_contextmenu.value = false;
}
}, {
name: "置于底层",
hotkey: "Ctrl+←",
enable: true,
fun: function () {
if (!this.enable) {
return;
}
hotkeyPutOnButtom(svgLists, select_svg);
display_contextmenu.value = false;
}
}, {
name: "置于上一层",
hotkey: "Ctrl+↑",
enable: true,
fun: function () {
if (!this.enable) {
return;
}
hotkeyPutOnUp(svgLists, select_svg);
display_contextmenu.value = false;
}
}, {
name: "置于下一层",
hotkey: "Ctrl+↓",
enable: true,
fun: function () {
if (!this.enable) {
return;
}
hotkeyPutOnDown(svgLists, select_svg);
display_contextmenu.value = false;
}
}]);
const set_svg_info: Ref<ISvgDataLists> = ref({
id: '', title: '', svgPositionX: 0, svgPositionY: 0
});
//svg
const select_svg: ISelectSvg = reactive({
id: '',
index: 0,
sPositionX: 0,
sPositionY: 0,
create_type: ''
});
//svg
const select_lefttool: Ref<IComponentInfo> = ref({});
//svg
const leftimg_lists: Ref<ILeftImgLists> = ref({
commonComponentList: [],
drawComponentList: [],
chartComponentList: []
});
//
const select_toolbar: Ref<string | undefined> = ref('');
//
const mouseInfo: IMouseInfo = reactive({
status: 0,
mPositionX: 0,
mPositionY: 0
});
const rightnav_open = ref(false);
/**
* @description: 从左侧工具栏拖动组件到画布触发的事件
* @param {*}
* @return {*}
*/
const dropEvent = (e: DragEvent) => {
//
if (Object.keys(select_lefttool.value).length < 1) {
//
return;
}
//
const create_svg: ISvgDataLists = {
id: `${new Date().getTime()}`,
type: select_lefttool.value.type,
title: select_lefttool.value.title,
svgPositionX: e.offsetX,
svgPositionY: e.offsetY,
angle: 0,
size: 1,
extend_attr: JSON.parse(JSON.stringify(select_lefttool.value.extend_attr))
}
svgLists.push(create_svg);
//
select_lefttool.value = {};
}
const dragEnterEvent = (e: DragEvent) => {
//dragenterdragover drop
rightnav_open.value = false;
e.preventDefault();
}
const dragOverEvent = (e: DragEvent) => {
//dragenterdragover drop
e.preventDefault();
}
/**
* @description: 设置要创建组件的信息
* @param {*} createsvg_info 选中的组件信息
* @return {*}
*/
const setCreatSvgInfo = (createsvg_info: IComponentInfo) => {
select_lefttool.value = createsvg_info;
select_toolbar.value = createsvg_info.type;
}
/**
* @description: 保存绘制组件后的数据和svgdom
* @param {*}
* @return {*}
*/
const saveSvgInfo = () => {
if (svgLists.length == 0) {
(topbar_dom_ref.value as any).saveSvgInfoRes({ code: 2, msg: '请先绘制图像!' });
return;
}
(topbar_dom_ref.value as any).saveSvgInfoRes(svgLists.length > 0 ? { code: 1, msg: '保存成功!' } : { code: 0, msg: '保存失败!' });
emit('saveSvgInfo', svgLists, svg_dom_ref.value)
}
/**
* @description: 鼠标点击画布上svg触发事件
* @param {*}
* @return {*}
*/
const mouseDownEvent = (selectsvg: ISvgDataLists, index: number, e: MouseEvent) => {
e.preventDefault();
e.cancelBubble = true;
//
select_lefttool.value = {};
//
({ id: select_svg.id, svgPositionX: select_svg.sPositionX, svgPositionY: select_svg.sPositionY } = selectsvg);
rightnav_open.value = false;
select_svg.index = index;
select_svg.create_type = "draggable"
mouseInfo.status = 1;
mouseInfo.mPositionX = e.clientX;
mouseInfo.mPositionY = e.clientY;
}
const mouseMoveEvent = (e: MouseEvent) => {
//
if (mouseInfo.status != 1 || !select_svg.id) {
return;
}
const { clientX, clientY } = e;
if (select_svg.create_type == "draggable") {
let new_select_svg = { ...select_svg };
new_select_svg.sPositionX += clientX - mouseInfo.mPositionX;
new_select_svg.sPositionY += clientY - mouseInfo.mPositionY;
//
({ sPositionX: svgLists[select_svg.index].svgPositionX, sPositionY: svgLists[select_svg.index].svgPositionY } = new_select_svg);
}
else if (select_svg.create_type == "draw") {
//
if (svgLists[select_svg.index].extend_attr?.startpoint_x?.val != null) {
svgLists[select_svg.index].extend_attr.startpoint_x.val = 0;
}
if (svgLists[select_svg.index].extend_attr?.startpoint_y?.val != null) {
svgLists[select_svg.index].extend_attr.startpoint_y.val = 0;
}
if (svgLists[select_svg.index].extend_attr?.endpoint_x?.val != null) {
svgLists[select_svg.index].extend_attr.endpoint_x.val = clientX - mouseInfo.mPositionX;
}
if (svgLists[select_svg.index].extend_attr?.endpoint_y?.val != null) {
svgLists[select_svg.index].extend_attr.endpoint_y.val = clientY - mouseInfo.mPositionY;
}
}
}
const mouseUpEvent = (e: MouseEvent) => {
//
if (mouseInfo.status != 1 || !select_svg.id) {
return;
}
mouseInfo.status = 0;
rightnav_open.value = true;
set_svg_info.value = svgLists[select_svg.index];
//
select_lefttool.value = {};
select_toolbar.value = '';
}
/**
* @description: 鼠标点击画布
* @param {*}
* @return {*}
*/
const mouseDownCanvasEvent = (e: MouseEvent) => {
//
if (Object.keys(select_lefttool.value).length < 1) {
rightnav_open.value = false;
select_svg.id = '';
return;
}
//
const create_svg: ISvgDataLists = {
id: `${new Date().getTime()}`,
type: select_lefttool.value.type,
title: select_lefttool.value.title,
svgPositionX: e.offsetX,
svgPositionY: e.offsetY,
angle: 0,
size: 1,
extend_attr: JSON.parse(JSON.stringify(select_lefttool.value.extend_attr))//
}
svgLists.push(create_svg);
//
({ id: select_svg.id, svgPositionX: select_svg.sPositionX, svgPositionY: select_svg.sPositionY } = create_svg);
rightnav_open.value = false;
select_svg.index = svgLists.length - 1;
select_svg.create_type = "draw"
mouseInfo.status = 1;
mouseInfo.mPositionX = e.clientX;
mouseInfo.mPositionY = e.clientY;
}
/**
* @description: 鼠标右键
* @param {*}
* @return {*}
*/
const contextmenuEvent = (e: MouseEvent) => {
e.preventDefault();
display_contextmenu.value = true;
(contextMenuRef.value as any).style.left = e.pageX + 'px';
(contextMenuRef.value as any).style.top = e.pageY + 'px';
contextmenu_data.map(m => m.enable = true);
//index
if (svgLists.length === 1) {
//
contextmenu_data[3].enable = false;
contextmenu_data[5].enable = false;
//
contextmenu_data[2].enable = false;
contextmenu_data[4].enable = false;
}
else if (select_svg.index === 0) {
//
contextmenu_data[3].enable = false;
contextmenu_data[5].enable = false;
}
else if (select_svg.index === svgLists.length - 1) {
//
contextmenu_data[2].enable = false;
contextmenu_data[4].enable = false;
}
}
/**
* @description: 点击页面其他位置隐藏右键菜单
* @param {*}
* @return {*}
*/
const documentClickEvent = (e: MouseEvent) => {
if (e.button !== 2) {
display_contextmenu.value = false;
}
}
watch(() => [...props.component_infos], (newval, oldval) => {
leftimg_lists.value = {
commonComponentList: newval.filter(f => f.panel_class == 'common'),
drawComponentList: newval.filter(f => f.panel_class == 'draw'),
chartComponentList: newval.filter(f => f.panel_class == 'chart')
};
});
//
document.onkeydown = function (e) {
//
if (!select_svg.id) {
return;
}
if (!e.ctrlKey && e.key == 'ArrowUp') {
e.preventDefault();
moveUp(svgLists, select_svg);
} else if (!e.ctrlKey && e.key == 'ArrowDown') {
e.preventDefault();
moveDown(svgLists, select_svg);
} else if (!e.ctrlKey && e.key == 'ArrowLeft') {
e.preventDefault();
moveLeft(svgLists, select_svg);
} else if (!e.ctrlKey && e.key == 'ArrowRight') {
e.preventDefault();
moveRight(svgLists, select_svg);
}
//ctrl c
else if (e.ctrlKey && e.key.toLowerCase() == 'c') {
e.preventDefault();
hotkeyCopy(svgLists, select_svg);
}
//deleted
else if (e.key == 'Delete') {
e.preventDefault();
hotkeyDel(svgLists, select_svg);
rightnav_open.value = false;
}
//
else if (e.ctrlKey && e.key == 'ArrowUp') {
e.preventDefault();
hotkeyPutOnUp(svgLists, select_svg);
}
//
else if (e.ctrlKey && e.key == 'ArrowDown') {
e.preventDefault();
hotkeyPutOnDown(svgLists, select_svg);
}
//
else if (e.ctrlKey && e.key == 'ArrowLeft') {
e.preventDefault();
hotkeyPutOnButtom(svgLists, select_svg);
}
//
else if (e.ctrlKey && e.key == 'ArrowRight') {
e.preventDefault();
hotkeyPutOnTop(svgLists, select_svg);
}
}
const setSvgLists = (new_val: ISvgDataLists[]) => {
svgLists.length = 0;
svgLists.push(...new_val);
emit('saveSvgInfo', svgLists, svg_dom_ref.value)
}
defineExpose({
setSvgLists
})
</script>
<template>
<div class="navtop">
<n-message-provider>
<top-tool-bar @saveSvgInfo="saveSvgInfo" ref="topbar_dom_ref"></top-tool-bar>
</n-message-provider>
</div>
<div class="ancestors" @mousedown="documentClickEvent">
<div class="navleft">
<n-message-provider>
<left-tool-bar
:left_imglists="leftimg_lists"
:select_toolbar="select_toolbar"
@setCreatSvgInfo="setCreatSvgInfo"
></left-tool-bar>
</n-message-provider>
</div>
<div
class="warp"
@drop="dropEvent"
@dragenter="dragEnterEvent"
@dragover="dragOverEvent"
@mousemove="mouseMoveEvent"
@mouseup="mouseUpEvent"
@mousedown="mouseDownCanvasEvent"
>
<svg
xmlns="http://www.w3.org/2000/svg"
style="background-color:#000000;"
:width="svgCanvas.width"
:height="svgCanvas.height"
:viewBox="`0 0 ${svgCanvas.width} ${svgCanvas.height}`"
ref="svg_dom_ref"
>
<defs />
<filter x="0" y="0" width="1" height="1" id="solid">
<feFlood flood-color="rgb(255,255,255)" />
<feComposite in="SourceGraphic" />
</filter>
<g
style="cursor:pointer"
:class="item.id == select_svg.id ? 'svg-selected' : ''"
v-for="(item,index) in svgLists"
:key="item.id"
:id="item.id"
:transform="'translate(' + (item.svgPositionX) + ',' + (item.svgPositionY) + ')' + 'rotate(' + item.angle + ')' + 'scale(' + item.size + ')'"
@mousedown="mouseDownEvent(item, index, $event)"
@contextmenu.stop="contextmenuEvent"
>
<svg-dynamic
:component_type="item.type"
:component_template="props.component_infos.filter(f => f.type == item.type)[0].template"
:component_props="props.component_infos.filter(f => f.type == item.type)[0].props"
:component_attr="item"
/>
</g>
</svg>
</div>
<div class="navright" v-show="rightnav_open">
<right-tool-bar :set_svg_info="set_svg_info"></right-tool-bar>
</div>
</div>
<div class="navbuttom">
<bottom-bar></bottom-bar>
</div>
<!-- 右键菜单 -->
<ul ref="contextMenuRef" class="contextMenu" v-show="display_contextmenu">
<li v-for="(item,index) in contextmenu_data" :key="index" @click="item.fun()">
<p :class="item.enable ? '' : 'disabled'">
{{ item.name }}
<span class="shortcut">{{ item.hotkey }}</span>
</p>
</li>
</ul>
</template>
<style scoped>
.contextMenu {
position: absolute;
z-index: 99999;
background: #ffffff;
padding: 5px 0;
margin: 0px;
display: block;
border-radius: 5px;
box-shadow: 2px 5px 10px rgba(0, 0, 0, 0.3);
}
.contextMenu li {
list-style: none;
padding: 0px;
margin: 0px;
}
.contextMenu .shortcut {
width: 115px;
text-align: right;
float: right;
}
.contextMenu p {
text-decoration: none;
display: block;
padding: 0px 15px 1px 20px;
margin: 0;
user-select: none;
-webkit-user-select: none;
}
.contextMenu p:hover {
background-color: #0cf;
color: #ffffff;
cursor: default;
}
.contextMenu .disabled {
color: #999;
}
.contextMenu .disabled:hover {
color: #999;
background-color: transparent;
}
.contextMenu li.separator {
border-top: solid 1px #e3e3e3;
padding-top: 5px;
margin-top: 5px;
}
.ancestors {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
}
.ancestors .navleft {
width: auto;
height: 100%;
/* border-left: solid 1px rgb(239, 239, 245); */
}
.ancestors .warp {
overflow: auto;
width: 100%;
height: 100%;
}
.ancestors .navright {
width: auto;
height: 100%;
/*background-color: aqua;*/
}
.navtop {
width: 100%;
height: 60px;
border-bottom: solid 1px rgb(239, 239, 245);
}
.navbuttom {
width: 100%;
height: 60px;
}
.svg-selected {
outline: 1px solid #0cf;
}
.warp::-webkit-scrollbar {
display: none;
}
</style>

@ -1,46 +0,0 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
style="background-color:#000000;"
width="100%"
height="100%"
:viewBox="`0 0 ${props.svgCanvas.width} ${props.svgCanvas.height}`"
>
<g
v-for="(item,index) in props.svg_data"
:key="item.id"
:id="item.id"
:transform="'translate(' + (item.svgPositionX) + ',' + (item.svgPositionY) + ')' + 'rotate(' + item.angle + ')' + 'scale(' + item.size + ')'"
>
<svg-dynamic
:component_type="item.type"
:component_template="props.component_infos.filter(f => f.type == item.type)[0].template"
:component_props="props.component_infos.filter(f => f.type == item.type)[0].props"
:component_attr="item"
/>
</g>
</svg>
</template>
<script setup lang="ts">
import SvgDynamic from "./SvgDynamic.vue";
import { ISvgDataLists, ISvgCanvas, IComponentInfo } from "../Model";
import "../assets/css/svgAnimation/index.css";
const props = defineProps({
//json
component_infos: {
type: Array as () => Array<IComponentInfo>,
required: true,
default: []
},
//
svg_data: {
type: Array as () => Array<ISvgDataLists>,
required: true,
default: []
},
svgCanvas: {
type: Object as () => ISvgCanvas,
default: { width: 1520, height: 720 }
}
});
</script>

@ -1,68 +0,0 @@
<template>
<div style="padding: 10px;display: flex;">
<img title="基于vue3+ts+svg的web组件编辑器" class="logoimg" src="../assets/logo.png" />
<span class="logotext">vue-webtopo-svgeditor</span>
<div style="display: flex;width: 100%;flex-direction: row-reverse;">
<a href="https://www.cnblogs.com/Hero-/p/14784744.html" target="_blank" class="a-link">
<n-button text>帮助</n-button>
</a>
<a href="https://github.com/yaolunmao/vue-webtopo-svgeditor" target="_blank" class="a-link">
<n-button text>Github</n-button>
</a>
<n-popover trigger="hover">
<template #trigger>
<n-button text class="button-class">v3.0</n-button>
</template>
<div>这个点了没什么用 不过会变色</div>
<div>说实话 这个版本号就是瞎写的 一点都不严谨</div>
</n-popover>
<n-button type="primary" class="button-class" @click="() => { emit('saveSvgInfo') }">保存</n-button>
</div>
</div>
</template>
<script setup lang="ts">
import { NButton, useMessage, NPopover } from "naive-ui";
const message = useMessage();
const emit = defineEmits(['saveSvgInfo']);
defineExpose({
/**
* @description: 保存svg结果 父组件调用用
* @param {*}type 0失败 1成功 2警告
* @return {*}
*/
saveSvgInfoRes(type: any) {
if (type?.code == 0) {
message.error(type?.msg);
}
else if (type?.code == 1) {
message.success(type?.msg);
}
else if (type?.code == 2) {
message.warning(type?.msg);
}
}
})
</script>
<style>
.logoimg {
height: 2rem;
min-width: 2rem;
margin-right: 0.1rem;
margin-top: 0.2rem;
}
.logotext {
font-size: 0.8rem;
font-weight: 700;
color: #2c3e50;
margin-top: 0.7rem;
width: 30%;
}
.a-link {
text-decoration: none;
margin-right: 1.5rem;
align-self: center;
}
.button-class {
margin-right: 1.5rem;
}
</style>

@ -0,0 +1,19 @@
<template>
<svg aria-hidden="true">
<use :xlink:href="symbolId" v-bind="props.props" />
</svg>
</template>
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps({
name: {
type: String,
required: true
},
props: {
type: Object,
default: () => {}
}
});
const symbolId = computed(() => `#svg-${props.name}`);
</script>

@ -0,0 +1,10 @@
<!-- eslint-disable vue/html-indent -->
<template>
<div class="flex justify-center items-center pt-2">
<div> Copyright (c) 2022</div>
<a class="mx-2 hover:text-blue-400" href="https://github.com/yaolunmao" target="_blank"
>咬轮猫</a
></div
>
</template>
<script lang="ts" setup></script>

@ -0,0 +1,874 @@
<!-- eslint-disable vue/html-indent -->
<template>
<div
class="canvas"
tabindex="0"
ref="canvasRef"
@drop="dropEvent"
@dragenter="dragEnterEvent"
@dragover="dragOverEvent"
@mousedown="onCanvasMouseDown"
@mousemove="onCanvasMouseMove"
@mouseup="onCanvasMouseUp"
@contextmenu="onCanvasContextMenuEvent"
@keydown="onHandleKeyDown"
>
<svg
xmlns="http://www.w3.org/2000/svg"
:style="{ backgroundColor: configStore.svg.background_color }"
width="100%"
height="100%"
>
<defs>
<pattern id="pattern_grid" patternUnits="userSpaceOnUse" x="0" y="0" width="10" height="10">
<rect width="1" height="1" rx="1" ry="1" fill="#aaaaaa" />
</pattern>
</defs>
<rect v-if="configStore.svg.grid" width="100%" height="100%" fill="url(#pattern_grid)" />
<g
:transform="`translate(${
configStore.svg.position_center.x + svgEditLayoutStore.center_offset.x
},${
configStore.svg.position_center.y + svgEditLayoutStore.center_offset.y
})rotate(${0})scale(${configStore.svg.scale})`"
>
<g
v-for="(item, index) in globalStore.done_json"
:key="item.id"
:transform="`translate(${item.x},${item.y})rotate(0)scale(1)`"
v-show="item.display"
>
<g
:transform="`translate(${item.actual_bound.x + item.actual_bound.width / 2},${
item.actual_bound.y + item.actual_bound.height / 2
})rotate(${item.rotate}) scale(1) translate(${-(
item.actual_bound.x +
item.actual_bound.width / 2
)},${-(item.actual_bound.y + item.actual_bound.height / 2)})`"
@mousedown="onSvgMouseDown(item, index, $event)"
@mouseenter="onSvgMouseEnter(item, index, $event)"
@mouseleave="onSvgMouseLeave(item, index, $event)"
@contextmenu="onSvgContextMenuEvent(item, index, $event)"
>
<connection-line
v-if="item.type === EDoneJsonType.ConnectionLine"
:item-info="item"
:point-visiable="
visiable_info.connection_line && visiable_info.select_item.info?.id == item.id
"
></connection-line>
<use
v-else-if="item.type === EDoneJsonType.File"
:xlink:href="`#svg-${item.name}`"
v-bind="prosToVBind(item)"
width="100"
height="100"
:id="item.id"
:transform="`translate(${item.actual_bound.x + item.actual_bound.width / 2},${
item.actual_bound.y + item.actual_bound.height / 2
}) scale(${item.scale_x},${item.scale_y}) translate(${-(
item.actual_bound.x +
item.actual_bound.width / 2
)},${-(item.actual_bound.y + item.actual_bound.height / 2)})`"
></use>
<component
v-else-if="item.type === EDoneJsonType.CustomSvg"
:is="item.tag"
v-bind="prosToVBind(item)"
width="100"
height="100"
:id="item.id"
:transform="`translate(${item.actual_bound.x + item.actual_bound.width / 2},${
item.actual_bound.y + item.actual_bound.height / 2
}) scale(${item.scale_x},${item.scale_y}) translate(${-(
item.actual_bound.x +
item.actual_bound.width / 2
)},${-(item.actual_bound.y + item.actual_bound.height / 2)})`"
></component>
<foreignObject
v-else-if="item.type === EDoneJsonType.Vue"
v-bind="getActualBoundScale(item.actual_bound, item.scale_x, item.scale_y)"
:id="`foreign-object${item.id}`"
>
<component
:is="item.tag"
v-bind="prosToVBind(item)"
:id="item.id"
:transform="`translate(${item.actual_bound.x + item.actual_bound.width / 2},${
item.actual_bound.y + item.actual_bound.height / 2
}) scale(${item.scale_x},${item.scale_y}) translate(${-(
item.actual_bound.x +
item.actual_bound.width / 2
)},${-(item.actual_bound.y + item.actual_bound.height / 2)})`"
>{{ item.tag_slot }}</component
>
</foreignObject>
<line
v-else-if="item.type === EDoneJsonType.StraightLine"
:id="item.id"
:x1="item.props.start_x.val"
:y1="item.props.start_y.val"
:x2="item.props.end_x.val"
:y2="item.props.end_y.val"
fill="#FF0000"
stroke="#FF0000"
stroke-width="2"
></line>
<rect
v-if="item.config.actual_rect"
:id="`rect${item.id}`"
fill="black"
fill-opacity="0"
v-bind="getActualBoundScale(item.actual_bound, item.scale_x, item.scale_y)"
style="stroke: none; stroke-width: 2; stroke-miterlimit: 10"
:class="`${
globalStore.intention == EGlobalStoreIntention.None ||
globalStore.intention == EGlobalStoreIntention.Select
? 'svg-item-none'
: ''
}
${
globalStore.intention == EGlobalStoreIntention.Move &&
globalStore.handle_svg_info?.info.id == item.id
? 'svg-item-move'
: ''
} ${
globalStore.intention == EGlobalStoreIntention.Select &&
globalStore.handle_svg_info?.info.id == item.id
? 'svg-item-select'
: ''
}`"
></rect>
<handle-panel
v-if="
globalStore.handle_svg_info?.info.id === item.id &&
visiable_info.handle_panel &&
item.config.can_zoom
"
:item-info="item"
></handle-panel>
<connection-panel
v-if="
visiable_info.select_item.info?.id == item.id &&
visiable_info.connection_panel &&
item.config.have_anchor &&
(globalStore.intention === EGlobalStoreIntention.Select
? item.id !== globalStore.handle_svg_info?.info.id
? true
: false
: true)
"
:item-info="item"
></connection-panel>
</g>
</g>
</g>
</svg>
<!-- 右键菜单 -->
<ul ref="contextMenuRef" class="contextMenu" v-show="contextMenuStore.display">
<li
v-for="(item, key) in contextMenuStore.info"
:key="item.title"
@click="contextMenuStore.onContextMenuClick(key)"
>
<p :class="item.enable ? '' : 'disabled'">
{{ item.title }}
<span class="shortcut">{{ item.hot_key }}</span>
</p>
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { computed, getCurrentInstance, onMounted, reactive, ref } from 'vue';
import { useConfigStore } from '@/store/config';
import { useGlobalStore } from '@/store/global';
import {
EGlobalStoreIntention,
EMouseInfoState,
EScaleInfoType,
IDoneJson
} from '@/store/global/types';
import { useSvgEditLayoutStore } from '@/store/svgedit-layout';
import {
getCenterPoint,
randomString,
getSvgNowPosition,
setSvgActualInfo,
prosToVBind,
objectDeepClone
} from '@/utils';
import {
calculateBottom,
calculateLeft,
calculateLeftBottom,
calculateLeftTop,
calculateRight,
calculateRightBottom,
calculateRightTop,
calculateTop
} from '@/utils/scale-core';
import HandlePanel from '@/components/webtopo-svg-edit/components/handle-panel/index.vue';
import ConnectionPanel from '@/components/webtopo-svg-edit/components/connection-panel/index.vue';
import { EDoneJsonType, IConfigItem } from '@/config-center/types';
import ConnectionLine from '@/components/webtopo-svg-edit/components/connection-line/index.vue';
import { IVisiableInfo } from './types';
import { ComponentImport } from '@/config-center';
import { useContextMenuStore, useEditPrivateStore } from '@/store/system';
import { EContextMenuInfoType } from '@/store/system/types';
import { useHistoryRecord } from '@/hooks';
// import HandlePanel from '../handle-panel/index.vue';
//
const instance = getCurrentInstance();
Object.keys(ComponentImport).forEach((key) => {
if (!Object.keys(instance?.appContext?.components as any).includes(key)) {
instance?.appContext.app.component(key, ComponentImport[key]);
}
});
const globalStore = useGlobalStore();
const configStore = useConfigStore();
const svgEditLayoutStore = useSvgEditLayoutStore();
const editPrivateStore = useEditPrivateStore();
const contextMenuStore = useContextMenuStore();
const contextMenuRef = ref<HTMLElement>();
const canvasRef = ref<HTMLElement>();
const cursor_style = computed(() =>
globalStore.intention == EGlobalStoreIntention.MoveCanvas
? 'grab'
: globalStore.intention == EGlobalStoreIntention.Rotate
? "url('/src/assets/icons/rotate.svg') 12 12, auto"
: 'default'
);
const visiable_info: IVisiableInfo = reactive({
handle_panel: computed(
() =>
globalStore.intention === EGlobalStoreIntention.Select ||
globalStore.intention === EGlobalStoreIntention.Zoom ||
globalStore.intention === EGlobalStoreIntention.Rotate
),
connection_panel: false,
connection_line: false,
select_item: {
info: null,
index: -1
}
});
const dropEvent = (e: DragEvent) => {
if (globalStore.intention == EGlobalStoreIntention.None) {
return;
} else if (globalStore.intention == EGlobalStoreIntention.Create) {
if (!globalStore.create_svg_info) {
console.error('要创建的数据获取失败');
return;
}
const done_item_json: IDoneJson = {
id: globalStore.create_svg_info.name + randomString(),
x: e.clientX - svgEditLayoutStore.center_offset.x,
y: e.clientY - svgEditLayoutStore.center_offset.y,
client: {
x: e.clientX - svgEditLayoutStore.center_offset.x,
y: e.clientY - svgEditLayoutStore.center_offset.y
},
scale_x: 1,
scale_y: 1,
rotate: 0,
actual_bound: {
x: 0,
y: 0,
width: 0,
height: 0
},
point_coordinate: {
tl: {
x: 0,
y: 0
},
tc: {
x: 0,
y: 0
},
tr: {
x: 0,
y: 0
},
l: {
x: 0,
y: 0
},
r: {
x: 0,
y: 0
},
bl: {
x: 0,
y: 0
},
bc: {
x: 0,
y: 0
},
br: {
x: 0,
y: 0
}
},
...objectDeepClone<IConfigItem>(globalStore.create_svg_info)
};
globalStore.setHandleSvgInfo(done_item_json, globalStore.done_json.length);
globalStore.setDoneJson(done_item_json);
globalStore.intention = EGlobalStoreIntention.None;
}
canvasRef.value?.focus();
};
const dragEnterEvent = (e: DragEvent) => {
//dragenterdragover drop
e.preventDefault();
};
const dragOverEvent = (e: DragEvent) => {
//dragenterdragover drop
e.preventDefault();
};
const onSvgMouseDown = (select_item: IDoneJson, index: number, e: MouseEvent) => {
canvasRef.value?.focus();
if (globalStore.intention === EGlobalStoreIntention.Connection) {
return;
}
e.preventDefault();
e.stopPropagation();
//
globalStore.intention = EGlobalStoreIntention.Select;
globalStore.setHandleSvgInfo(select_item, index);
globalStore.setMouseInfo({
state: EMouseInfoState.Down,
position_x: e.clientX,
position_y: e.clientY,
now_position_x: select_item.x,
now_position_y: select_item.y,
new_position_x: select_item.x,
new_position_y: select_item.y
});
};
const onSvgMouseEnter = (select_item: IDoneJson, index: number, e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
visiable_info.connection_panel = true;
visiable_info.connection_line = true;
if (
(globalStore.intention === EGlobalStoreIntention.Connection ||
globalStore.intention === EGlobalStoreIntention.SetConnectionLineNode) &&
select_item.type === EDoneJsonType.ConnectionLine
) {
return;
}
visiable_info.select_item.info = select_item;
visiable_info.select_item.index = index;
};
const onSvgMouseLeave = (select_item: IDoneJson, index: number, e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
if (
(globalStore.intention === EGlobalStoreIntention.Connection ||
globalStore.intention === EGlobalStoreIntention.SetConnectionLineNode) &&
select_item.type === EDoneJsonType.ConnectionLine
) {
return;
}
visiable_info.connection_panel = false;
visiable_info.connection_line = false;
visiable_info.select_item.info = null;
visiable_info.select_item.index = -1;
};
const onCanvasMouseMove = (e: MouseEvent) => {
// 线
if (
globalStore.mouse_info.state != EMouseInfoState.Down &&
globalStore.intention !== EGlobalStoreIntention.Connection
) {
return;
}
const { clientX, clientY } = e;
globalStore.mouse_info.new_position_x =
globalStore.mouse_info.now_position_x + clientX - globalStore.mouse_info.position_x;
globalStore.mouse_info.new_position_y =
globalStore.mouse_info.now_position_y + clientY - globalStore.mouse_info.position_y;
if (
globalStore.handle_svg_info?.info &&
(globalStore.intention == EGlobalStoreIntention.Select ||
globalStore.intention == EGlobalStoreIntention.Move)
) {
//
globalStore.handle_svg_info.info.x = globalStore.mouse_info.new_position_x;
globalStore.handle_svg_info.info.y = globalStore.mouse_info.new_position_y;
globalStore.handle_svg_info.info.client = {
x: globalStore.mouse_info.new_position_x,
y: globalStore.mouse_info.new_position_y
};
globalStore.intention = EGlobalStoreIntention.Move;
} else if (globalStore.intention == EGlobalStoreIntention.MoveCanvas) {
//
svgEditLayoutStore.center_offset.x = globalStore.mouse_info.new_position_x;
svgEditLayoutStore.center_offset.y = globalStore.mouse_info.new_position_y;
} else if (globalStore.intention === EGlobalStoreIntention.Zoom) {
if (!globalStore.handle_svg_info) {
return;
}
//
const curPositon = {
x: e.clientX - svgEditLayoutStore.center_offset.x,
y: e.clientY - svgEditLayoutStore.center_offset.y
};
let new_length = {
width: 0,
height: 0,
is_old_width: false,
is_old_height: false
};
if (globalStore.scale_info.type === EScaleInfoType.TopLeft) {
new_length = calculateLeftTop(
curPositon,
globalStore.scale_info.symmetric_point,
globalStore.handle_svg_info.info.rotate
);
} else if (globalStore.scale_info.type === EScaleInfoType.TopRight) {
new_length = calculateRightTop(
curPositon,
globalStore.scale_info.symmetric_point,
globalStore.handle_svg_info.info.rotate
);
} else if (globalStore.scale_info.type === EScaleInfoType.BottomRight) {
new_length = calculateRightBottom(
curPositon,
globalStore.scale_info.symmetric_point,
globalStore.handle_svg_info.info.rotate
);
} else if (globalStore.scale_info.type === EScaleInfoType.BottomLeft) {
new_length = calculateLeftBottom(
curPositon,
globalStore.scale_info.symmetric_point,
globalStore.handle_svg_info.info.rotate
);
} else if (globalStore.scale_info.type === EScaleInfoType.TopCenter) {
new_length = calculateTop(
curPositon,
globalStore.scale_info.symmetric_point,
globalStore.handle_svg_info.info.rotate,
globalStore.handle_svg_info.info.client
);
} else if (globalStore.scale_info.type === EScaleInfoType.Right) {
new_length = calculateRight(
curPositon,
globalStore.scale_info.symmetric_point,
globalStore.handle_svg_info.info.rotate,
globalStore.handle_svg_info.info.client
);
} else if (globalStore.scale_info.type === EScaleInfoType.BottomCenter) {
new_length = calculateBottom(
curPositon,
globalStore.scale_info.symmetric_point,
globalStore.handle_svg_info.info.rotate,
globalStore.handle_svg_info.info.client
);
} else if (globalStore.scale_info.type === EScaleInfoType.Left) {
new_length = calculateLeft(
curPositon,
globalStore.scale_info.symmetric_point,
globalStore.handle_svg_info.info.rotate,
globalStore.handle_svg_info.info.client
);
}
//
// const move_length_x =
// globalStore.scale_info.type === EScaleInfoType.TopLeft ||
// globalStore.scale_info.type === EScaleInfoType.Left ||
// globalStore.scale_info.type === EScaleInfoType.BottomLeft
// ? -(newTopLeftPoint.x - globalStore.mouse_info.now_position_x)
// : globalStore.scale_info.type === EScaleInfoType.TopRight ||
// globalStore.scale_info.type === EScaleInfoType.Right ||
// globalStore.scale_info.type === EScaleInfoType.BottomRight
// ? globalStore.mouse_info.now_position_x - newTopLeftPoint.x
// : 0;
// const move_length_y =
// globalStore.scale_info.type === EScaleInfoType.TopLeft ||
// globalStore.scale_info.type === EScaleInfoType.TopCenter ||
// globalStore.scale_info.type === EScaleInfoType.TopRight
// ? newTopLeftPoint.y - globalStore.mouse_info.now_position_y
// : globalStore.scale_info.type === EScaleInfoType.BottomLeft ||
// globalStore.scale_info.type === EScaleInfoType.BottomCenter ||
// globalStore.scale_info.type === EScaleInfoType.BottomRight
// ? globalStore.mouse_info.now_position_y - newTopLeftPoint.y
// : 0;
//
if (globalStore.handle_svg_info && new_length.width > 0 && new_length.height > 0) {
const scale_x = !new_length.is_old_width
? new_length.width / globalStore.handle_svg_info.info.actual_bound.width
: 1;
const scale_y = !new_length.is_old_height
? new_length.height / globalStore.handle_svg_info.info.actual_bound.height
: 1;
const newCenterPoint = getCenterPoint(curPositon, globalStore.scale_info.symmetric_point);
if (
scale_x > 0 &&
globalStore.scale_info.type !== EScaleInfoType.TopCenter &&
globalStore.scale_info.type !== EScaleInfoType.BottomCenter
) {
globalStore.handle_svg_info.info.scale_x = scale_x;
globalStore.handle_svg_info.info.x = getSvgNowPosition(
globalStore.handle_svg_info.info.client.x,
newCenterPoint.x,
globalStore.scale_info.scale_item_info.x
);
}
if (
scale_y > 0 &&
globalStore.scale_info.type !== EScaleInfoType.Left &&
globalStore.scale_info.type !== EScaleInfoType.Right
) {
globalStore.handle_svg_info.info.scale_y = scale_y;
globalStore.handle_svg_info.info.y = getSvgNowPosition(
globalStore.handle_svg_info.info.client.y,
newCenterPoint.y,
globalStore.scale_info.scale_item_info.y
);
}
}
} else if (globalStore.intention === EGlobalStoreIntention.Rotate) {
if (!globalStore.handle_svg_info) {
return;
}
const rotateDegreeBefore =
Math.atan2(
globalStore.mouse_info.position_y - globalStore.handle_svg_info.info.client.y,
globalStore.mouse_info.position_x - globalStore.handle_svg_info.info.client.x
) /
(Math.PI / 180);
const rotateDegreeAfter =
Math.atan2(
clientY - svgEditLayoutStore.center_offset.y - globalStore.handle_svg_info.info.client.y,
clientX - svgEditLayoutStore.center_offset.x - globalStore.handle_svg_info.info.client.x
) /
(Math.PI / 180);
globalStore.handle_svg_info.info.rotate =
globalStore.rotate_info.angle + rotateDegreeAfter - rotateDegreeBefore;
} else if (
globalStore.intention === EGlobalStoreIntention.Connection &&
globalStore.handle_svg_info
) {
globalStore.handle_svg_info.info.props.point_position.val[
globalStore.handle_svg_info?.info.props.point_position.val.length - 1
] = {
x: getSvgNowPosition(
globalStore.mouse_info.position_x,
clientX,
globalStore.handle_svg_info?.info.props.point_position.val[0].x
),
y: getSvgNowPosition(
globalStore.mouse_info.position_y,
clientY,
globalStore.handle_svg_info?.info.props.point_position.val[0].y
)
};
// console.log('线', start_x, start_y, end_x, end_y, clientX, clientY);
} else if (
globalStore.intention === EGlobalStoreIntention.SetConnectionLineNode &&
globalStore.handle_svg_info
) {
globalStore.handle_svg_info.info.props.point_position.val[
globalStore.connection_line_node_info.point_index
] = {
x: getSvgNowPosition(
globalStore.mouse_info.position_x,
clientX,
globalStore.connection_line_node_info.init_pos.x
),
y: getSvgNowPosition(
globalStore.mouse_info.position_y,
clientY,
globalStore.connection_line_node_info.init_pos.y
)
};
}
};
const onCanvasMouseUp = () => {
//
if (globalStore.mouse_info.state != EMouseInfoState.Down) {
return;
}
if (globalStore.handle_svg_info?.info && globalStore.intention == EGlobalStoreIntention.Move) {
globalStore.done_json[globalStore.handle_svg_info.index].x =
globalStore.mouse_info.new_position_x;
globalStore.done_json[globalStore.handle_svg_info.index].y =
globalStore.mouse_info.new_position_y;
// globalStore.setDoneJson(globalStore.done_json);
setSvgActualInfo(globalStore.done_json[globalStore.handle_svg_info.index]);
globalStore.intention = EGlobalStoreIntention.None;
//
globalStore.setDoneJson(globalStore.done_json);
// globalStore.setHandleSvgInfo(undefined, 0);
} else if (
globalStore.handle_svg_info?.info &&
globalStore.intention == EGlobalStoreIntention.Zoom
) {
//
// const newCenterPoint = getCenterPoint(
// { x: e.clientX, y: e.clientY },
// globalStore.scale_info.symmetric_point
// );
globalStore.handle_svg_info.info.client = {
x: globalStore.handle_svg_info.info.x,
y: globalStore.handle_svg_info.info.y
};
globalStore.intention = EGlobalStoreIntention.None;
setSvgActualInfo(globalStore.done_json[globalStore.handle_svg_info.index]);
//
globalStore.setDoneJson(globalStore.done_json);
} else if (
globalStore.intention === EGlobalStoreIntention.Rotate &&
globalStore.handle_svg_info?.info
) {
setSvgActualInfo(globalStore.done_json[globalStore.handle_svg_info.index]);
//
globalStore.setDoneJson(globalStore.done_json);
} else if (globalStore.intention === EGlobalStoreIntention.Connection) {
return;
} else if (globalStore.intention != EGlobalStoreIntention.Select) {
globalStore.intention = EGlobalStoreIntention.None;
}
globalStore.setMouseInfo({
state: EMouseInfoState.Up,
position_x: 0,
position_y: 0,
now_position_x: 0,
now_position_y: 0,
new_position_x: 0,
new_position_y: 0
});
contextMenuStore.display = false;
};
const onCanvasMouseDown = (e: MouseEvent) => {
const { clientX, clientY } = e;
if (globalStore.intention === EGlobalStoreIntention.Connection && globalStore.handle_svg_info) {
if (e.button === 0) {
//线
globalStore.handle_svg_info.info.props.point_position.val.push({
x: getSvgNowPosition(
globalStore.mouse_info.position_x,
clientX,
globalStore.handle_svg_info?.info.props.point_position.val[0].x
),
y: getSvgNowPosition(
globalStore.mouse_info.position_y,
clientY,
globalStore.handle_svg_info?.info.props.point_position.val[0].y
)
});
}
if (e.button === 2) {
//线
globalStore.intention = EGlobalStoreIntention.None;
setSvgActualInfo(globalStore.done_json[globalStore.handle_svg_info.index]);
}
return;
}
//
globalStore.intention = EGlobalStoreIntention.MoveCanvas;
globalStore.setMouseInfo({
state: EMouseInfoState.Down,
position_x: clientX,
position_y: clientY,
now_position_x: svgEditLayoutStore.center_offset.x,
now_position_y: svgEditLayoutStore.center_offset.y,
new_position_x: svgEditLayoutStore.center_offset.x,
new_position_y: svgEditLayoutStore.center_offset.y
});
};
/**
* 鼠标右键事件
* @param select_component
* @param e
* @returns
*/
const onCanvasContextMenuEvent = (e: MouseEvent) => {
e.preventDefault(); //
};
const onSvgContextMenuEvent = (select_item: IDoneJson, index: number, e: MouseEvent) => {
if (globalStore.intention === EGlobalStoreIntention.Connection) {
return;
}
if (contextMenuRef.value) {
globalStore.intention = EGlobalStoreIntention.ContextMenu;
globalStore.setHandleSvgInfo(select_item, index);
contextMenuRef.value.style.left = e.pageX + 'px';
contextMenuRef.value.style.top = e.pageY + 'px';
contextMenuStore.info.MoveUpOneLevel.enable =
contextMenuStore.info.MoveUpTopLevel.enable =
contextMenuStore.info.MoveDownOneLevel.enable =
contextMenuStore.info.MoveDownTopLevel.enable =
true;
if (index === globalStore.done_json.length - 1) {
contextMenuStore.info.MoveUpOneLevel.enable = contextMenuStore.info.MoveUpTopLevel.enable =
false;
}
if (index === 0) {
contextMenuStore.info.MoveDownOneLevel.enable =
contextMenuStore.info.MoveDownTopLevel.enable = false;
}
contextMenuStore.display = true;
}
};
const getActualBoundScale = (
actual_bound: {
x: number;
y: number;
width: number;
height: number;
},
scale_x: number,
scale_y: number
) => {
return {
x: actual_bound.x - (actual_bound.width / 2) * scale_x + actual_bound.width / 2,
y: actual_bound.y - (actual_bound.height / 2) * scale_y + actual_bound.height / 2,
width: actual_bound.width * scale_x,
height: actual_bound.height * scale_y
};
};
const onHandleKeyDown = (e: KeyboardEvent) => {
console.log(e, 733);
e.preventDefault();
if (globalStore.handle_svg_info && !e.ctrlKey && e.key == 'ArrowUp') {
globalStore.done_json[globalStore.handle_svg_info.index].y -= 1;
useHistoryRecord(globalStore.done_json);
} else if (globalStore.handle_svg_info && !e.ctrlKey && e.key == 'ArrowDown') {
globalStore.handle_svg_info.info.y += 1;
useHistoryRecord(globalStore.done_json);
} else if (globalStore.handle_svg_info && !e.ctrlKey && e.key == 'ArrowLeft') {
globalStore.handle_svg_info.info.x -= 1;
useHistoryRecord(globalStore.done_json);
} else if (globalStore.handle_svg_info && !e.ctrlKey && e.key == 'ArrowRight') {
globalStore.handle_svg_info.info.x += 1;
useHistoryRecord(globalStore.done_json);
}
//ctrl c
else if (e.ctrlKey && e.key.toLowerCase() == 'c') {
contextMenuStore.onContextMenuClick(EContextMenuInfoType.Copy);
}
//deleted
else if (!e.ctrlKey && e.key == 'Delete') {
contextMenuStore.onContextMenuClick(EContextMenuInfoType.Delete);
}
//
else if (e.ctrlKey && e.key == 'ArrowUp') {
contextMenuStore.onContextMenuClick(EContextMenuInfoType.MoveUpOneLevel);
}
//
else if (e.ctrlKey && e.key == 'ArrowDown') {
contextMenuStore.onContextMenuClick(EContextMenuInfoType.MoveDownOneLevel);
}
//
else if (e.ctrlKey && e.key == 'ArrowLeft') {
contextMenuStore.onContextMenuClick(EContextMenuInfoType.MoveDownTopLevel);
}
//
else if (e.ctrlKey && e.key == 'ArrowRight') {
contextMenuStore.onContextMenuClick(EContextMenuInfoType.MoveUpTopLevel);
}
//ctrl+shift+z
else if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() == 'z') {
editPrivateStore.topRedoBtnClick();
}
//ctrl+z
else if (e.ctrlKey && e.key.toLowerCase() == 'z') {
editPrivateStore.topUndoBtnClick();
}
//ctrl+delete
else if (e.ctrlKey && e.key.toLowerCase() == 'delete') {
globalStore.done_json.length <= 0 || globalStore.setDoneJson([]);
}
};
onMounted(() => {
canvasRef.value?.focus();
});
</script>
<style lang="less" scoped>
.canvas {
width: 100%;
height: 100%;
cursor: v-bind('cursor_style');
&:focus-visible {
outline: 0px;
}
}
.svg-item-none {
cursor: move;
&:hover {
outline: 1px solid #0cf;
}
}
.svg-item-move {
cursor: move;
outline: 1px dashed rgb(23, 222, 30);
}
.svg-item-select {
cursor: move;
outline: 1px solid rgb(23, 222, 30);
}
.contextMenu {
position: absolute;
z-index: 99999;
background: #ffffff;
padding: 5px 0;
margin: 0px;
display: block;
border-radius: 5px;
box-shadow: 2px 5px 10px rgba(0, 0, 0, 0.3);
li {
list-style: none;
padding: 0px;
margin: 0px;
}
.shortcut {
width: 115px;
text-align: right;
float: right;
}
p {
text-decoration: none;
display: block;
padding: 0px 15px 1px 20px;
margin: 0;
user-select: none;
-webkit-user-select: none;
}
p:hover {
background-color: #0cf;
color: #ffffff;
cursor: default;
}
.disabled {
color: #999;
}
.disabled:hover {
color: #999;
background-color: transparent;
}
li.separator {
border-top: solid 1px #e3e3e3;
padding-top: 5px;
margin-top: 5px;
}
}
</style>

@ -0,0 +1,11 @@
import { IDoneJson } from '@/store/global/types';
export interface IVisiableInfo {
handle_panel: boolean;
connection_panel: boolean;
connection_line: boolean;
select_item: {
info: null | IDoneJson;
index: number;
};
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save