refactor: 初始化项目
@ -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的左括号之前使用一致的空格
|
||||
}
|
||||
};
|
@ -1,5 +1,24 @@
|
||||
# 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
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
@ -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"]
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 咬轮猫
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -0,0 +1 @@
|
||||
module.exports = { extends: ['@commitlint/config-conventional'] };
|
@ -1,39 +1,40 @@
|
||||
{
|
||||
"name": "vue-webtopo-svgeditor",
|
||||
"version": "0.0.4",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"module": "./dist/vue-webtopo-svgeditor.es.ts",
|
||||
"main": "./dist/vue-webtopo-svgeditor.umd.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/vue-webtopo-svgeditor.es.ts",
|
||||
"require": "./dist/vue-webtopo-svgeditor.umd.ts"
|
||||
},
|
||||
"./dist/style.css": {
|
||||
"import": "./dist/style.css",
|
||||
"require": "./dist/style.css"
|
||||
}
|
||||
},
|
||||
"version": "0.0.5",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.22.0",
|
||||
"vue": "^3.2.6"
|
||||
"vue": "^3.2.25",
|
||||
"vue-router": "4"
|
||||
},
|
||||
"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",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.5.4",
|
||||
"vite": "^2.9.9",
|
||||
"vite-plugin-eslint": "^1.6.1",
|
||||
"vue-eslint-parser": "^9.0.3",
|
||||
"vue-tsc": "^0.34.7"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,vue,ts,tsx}": [
|
||||
"pnpm run lint:eslint"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 4.4 KiB |
@ -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>
|
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 3.3 KiB |
@ -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;
|
||||
}
|
||||
}
|
@ -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) => {
|
||||
//dragenter和dragover一定要阻止浏览器默认行为 不然不会触发drop
|
||||
rightnav_open.value = false;
|
||||
e.preventDefault();
|
||||
}
|
||||
const dragOverEvent = (e: DragEvent) => {
|
||||
//dragenter和dragover一定要阻止浏览器默认行为 不然不会触发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>
|
@ -1,8 +1,8 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue'
|
||||
import type { DefineComponent } from 'vue';
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
|
@ -1,4 +0,0 @@
|
||||
import SvgEditor from './components/SvgEditor.vue';
|
||||
import SvgPrview from './components/SvgPrview.vue';
|
||||
import ECharts from 'vue-echarts';
|
||||
export { SvgEditor,SvgPrview,ECharts as VChart }
|
@ -1,105 +0,0 @@
|
||||
import { IComponentInfo, ISvgDataLists, ISvgCanvas, ILeftImgLists, IMouseInfo, ISelectSvg } from "../Model";
|
||||
/**
|
||||
* 组件上移
|
||||
* @param svgLists
|
||||
* @param select_svg
|
||||
*/
|
||||
export function moveUp(svgLists: ISvgDataLists[], select_svg: ISelectSvg) {
|
||||
svgLists[select_svg.index].svgPositionY -= 1;
|
||||
}
|
||||
/**
|
||||
* 组件下移
|
||||
* @param svgLists
|
||||
* @param select_svg
|
||||
*/
|
||||
export function moveDown(svgLists: ISvgDataLists[], select_svg: ISelectSvg) {
|
||||
svgLists[select_svg.index].svgPositionY += 1;
|
||||
}
|
||||
/**
|
||||
* 组件左移
|
||||
* @param svgLists
|
||||
* @param select_svg
|
||||
*/
|
||||
export function moveLeft(svgLists: ISvgDataLists[], select_svg: ISelectSvg) {
|
||||
svgLists[select_svg.index].svgPositionX -= 1;
|
||||
}
|
||||
/**
|
||||
* 组件右移
|
||||
* @param svgLists
|
||||
* @param select_svg
|
||||
*/
|
||||
export function moveRight(svgLists: ISvgDataLists[], select_svg: ISelectSvg) {
|
||||
svgLists[select_svg.index].svgPositionX += 1;
|
||||
}
|
||||
/**
|
||||
* 组件复制
|
||||
* @param svgLists
|
||||
* @param select_svg
|
||||
*/
|
||||
export function hotkeyCopy(svgLists: ISvgDataLists[], select_svg: ISelectSvg) {
|
||||
svgLists.push({
|
||||
...(JSON.parse(JSON.stringify(svgLists[select_svg.index]))),
|
||||
id: `${new Date().getTime()}`,
|
||||
svgPositionX: svgLists[select_svg.index].svgPositionX + 10,
|
||||
svgPositionY: svgLists[select_svg.index].svgPositionY + 10,
|
||||
title: svgLists[select_svg.index].title + `-copy`
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 组件删除
|
||||
* @param svgLists
|
||||
* @param select_svg
|
||||
*/
|
||||
export function hotkeyDel(svgLists: ISvgDataLists[], select_svg: ISelectSvg) {
|
||||
svgLists.splice(select_svg.index, 1);
|
||||
}
|
||||
/**
|
||||
* 组件置于顶层
|
||||
* @param svgLists
|
||||
* @param select_svg
|
||||
*/
|
||||
export function hotkeyPutOnTop(svgLists: ISvgDataLists[], select_svg: ISelectSvg) {
|
||||
const temp = svgLists[select_svg.index];
|
||||
svgLists.splice(select_svg.index, 1);
|
||||
svgLists.push(temp);
|
||||
select_svg.index = svgLists.length - 1;
|
||||
}
|
||||
/**
|
||||
* 组件置于底层
|
||||
* @param svgLists
|
||||
* @param select_svg
|
||||
*/
|
||||
export function hotkeyPutOnButtom(svgLists: ISvgDataLists[], select_svg: ISelectSvg) {
|
||||
const temp = svgLists[select_svg.index];
|
||||
svgLists.splice(select_svg.index, 1);
|
||||
svgLists.unshift(temp);
|
||||
select_svg.index = 0;
|
||||
}
|
||||
/**
|
||||
* 组件上移一层
|
||||
* @param svgLists
|
||||
* @param select_svg
|
||||
*/
|
||||
export function hotkeyPutOnUp(svgLists: ISvgDataLists[], select_svg: ISelectSvg) {
|
||||
if (svgLists.length === 1 || select_svg.index === svgLists.length - 1) {
|
||||
return;
|
||||
}
|
||||
const temp = svgLists[select_svg.index];
|
||||
svgLists[select_svg.index] = svgLists[select_svg.index + 1];
|
||||
svgLists[select_svg.index + 1] = temp;
|
||||
select_svg.index += 1;
|
||||
}
|
||||
/**
|
||||
* 组件下移一层
|
||||
* @param svgLists
|
||||
* @param select_svg
|
||||
*/
|
||||
export function hotkeyPutOnDown(svgLists: ISvgDataLists[], select_svg: ISelectSvg) {
|
||||
if (svgLists.length === 1 || select_svg.index === 0) {
|
||||
return;
|
||||
}
|
||||
const temp = svgLists[select_svg.index];
|
||||
svgLists[select_svg.index] = svgLists[select_svg.index - 1];
|
||||
svgLists[select_svg.index - 1] = temp;
|
||||
select_svg.index -= 1;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import ECharts from 'vue-echarts';
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
import router from './router/index';
|
||||
const app = createApp(App);
|
||||
app.component('v-chart', ECharts);
|
||||
app.use(router);
|
||||
app.mount('#app');
|
||||
|
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div>编辑页</div>
|
||||
</template>
|
@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div>预览页</div>
|
||||
</template>
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|