├── src
├── components
│ ├── mt-dzr
│ │ ├── composables
│ │ │ └── mouse.ts
│ │ ├── index.ts
│ │ ├── utils
│ │ │ └── types.ts
│ │ ├── components
│ │ │ ├── render-item.vue
│ │ │ ├── rotate-handle.vue
│ │ │ └── resize-handle.vue
│ │ ├── __tests__
│ │ │ └── mt-dzr.spec.ts
│ │ ├── store
│ │ │ ├── types.ts
│ │ │ └── index.ts
│ │ └── types.ts
│ ├── mt-edit
│ │ ├── index.ts
│ │ ├── components
│ │ │ ├── selected-area
│ │ │ │ ├── types.ts
│ │ │ │ └── index.vue
│ │ │ ├── render-core
│ │ │ │ └── types.ts
│ │ │ ├── layout
│ │ │ │ ├── footer-panel
│ │ │ │ │ └── index.vue
│ │ │ │ └── right-aside
│ │ │ │ │ ├── select-item-event-setting
│ │ │ │ │ └── input-target-value.vue
│ │ │ │ │ ├── json-edit.vue
│ │ │ │ │ ├── index.vue
│ │ │ │ │ ├── select-item-props-setting.vue
│ │ │ │ │ └── select-item-animate-setting
│ │ │ │ │ ├── index.vue
│ │ │ │ │ └── common-animate.vue
│ │ │ ├── types.ts
│ │ │ ├── svg-analysis
│ │ │ │ └── index.vue
│ │ │ ├── custom-svg-render
│ │ │ │ └── index.vue
│ │ │ ├── svg-render
│ │ │ │ └── index.vue
│ │ │ ├── export-json
│ │ │ │ └── index.vue
│ │ │ ├── import-json
│ │ │ │ └── index.vue
│ │ │ ├── group-render
│ │ │ │ └── index.vue
│ │ │ ├── drag-canvas
│ │ │ │ └── index.vue
│ │ │ ├── done-tree
│ │ │ │ └── index.vue
│ │ │ ├── context-menu
│ │ │ │ └── index.vue
│ │ │ ├── pattern-grid
│ │ │ │ └── index.vue
│ │ │ ├── render-item
│ │ │ │ └── index.vue
│ │ │ └── draw-line-render
│ │ │ │ └── index.vue
│ │ ├── assets
│ │ │ └── css
│ │ │ │ └── custom_ani.css
│ │ ├── store
│ │ │ ├── cache.ts
│ │ │ ├── left-aside.ts
│ │ │ ├── context-menu.ts
│ │ │ ├── global.ts
│ │ │ └── types.ts
│ │ ├── composables
│ │ │ ├── index.ts
│ │ │ ├── thumbnail.ts
│ │ │ └── sys-line.ts
│ │ └── ace-edit.ts
│ ├── mt-preview
│ │ ├── index.ts
│ │ └── index.vue
│ ├── test
│ │ ├── my-button
│ │ │ └── index.vue
│ │ ├── my-input
│ │ │ └── index.vue
│ │ ├── pie-charts
│ │ │ └── index.vue
│ │ └── custom-demo
│ │ │ └── index.vue
│ └── custom-components
│ │ ├── text-vue
│ │ └── index.vue
│ │ ├── card-vue
│ │ └── index.vue
│ │ ├── sys-button-vue
│ │ └── index.vue
│ │ ├── kv-vue
│ │ └── index.vue
│ │ └── now-time-vue
│ │ └── index.vue
├── assets
│ ├── main.css
│ ├── logo.png
│ ├── imgs
│ │ └── test
│ │ │ └── my-img.png
│ ├── icons
│ │ ├── menu-fold.svg
│ │ ├── menu-unfold.svg
│ │ ├── return.svg
│ │ ├── view-show.svg
│ │ ├── delete.svg
│ │ ├── upload.svg
│ │ ├── full-screen.svg
│ │ ├── preview.svg
│ │ ├── export-json.svg
│ │ ├── import-json.svg
│ │ ├── vertical-distribution.svg
│ │ ├── horizontal-distribution.svg
│ │ ├── tree-list.svg
│ │ ├── thumbnail.svg
│ │ ├── save.svg
│ │ ├── view-hide.svg
│ │ ├── align-left.svg
│ │ ├── align-right.svg
│ │ ├── align-vertical.svg
│ │ ├── align-bottom.svg
│ │ ├── align-horizontally.svg
│ │ ├── align-top.svg
│ │ ├── add.svg
│ │ ├── help.svg
│ │ ├── rotate.svg
│ │ ├── redo.svg
│ │ ├── unlock.svg
│ │ ├── undo.svg
│ │ ├── lock.svg
│ │ ├── exit-full-screen.svg
│ │ ├── align.svg
│ │ ├── search.svg
│ │ ├── pen-line.svg
│ │ ├── dark.svg
│ │ ├── line.svg
│ │ ├── ungroup.svg
│ │ ├── group.svg
│ │ ├── question.svg
│ │ ├── setting.svg
│ │ └── light.svg
│ ├── svgs
│ │ └── electrical
│ │ │ ├── 手车02.svg
│ │ │ ├── stroke
│ │ │ ├── 电动机.svg
│ │ │ ├── 熔断器.svg
│ │ │ ├── 三绕组自耦变压器.svg
│ │ │ ├── 手车01.svg
│ │ │ ├── 电容器.svg
│ │ │ ├── 接地.svg
│ │ │ ├── 电缆终端头.svg
│ │ │ ├── 电抗器.svg
│ │ │ ├── 隔离开关-刀闸.svg
│ │ │ ├── 自动空气断路器.svg
│ │ │ ├── 断路器-开关.svg
│ │ │ ├── 跌落式熔断器.svg
│ │ │ └── 消弧线圈.svg
│ │ │ ├── fs
│ │ │ ├── 电阻(阻抗).svg
│ │ │ ├── 火花间隙.svg
│ │ │ ├── 故障.svg
│ │ │ └── 避雷器.svg
│ │ │ ├── 双绕组变压器.svg
│ │ │ ├── 交流发电机.svg
│ │ │ └── 三绕组变压器.svg
│ └── css-vars.css
├── export.ts
├── main.ts
├── views
│ ├── preview
│ │ └── index.vue
│ ├── edit
│ │ └── index.vue
│ └── demo
│ │ ├── index.vue
│ │ ├── set-node-attr.vue
│ │ ├── event-callback.vue
│ │ ├── change-attr.vue
│ │ └── edit-load.vue
├── router
│ └── index.ts
└── App.vue
├── env.d.ts
├── public
├── imgs
│ ├── edit.png
│ ├── change-attr.png
│ ├── edit-load.png
│ ├── set-node-attr.gif
│ └── event-callback.png
├── svgs
│ ├── my-button.svg
│ ├── pie-charts.svg
│ ├── my-input.svg
│ └── demo.svg
└── favicon.svg
├── .vscode
├── extensions.json
└── settings.json
├── .prettierrc.json
├── tsconfig.vitest.json
├── tsconfig.json
├── tsconfig.app.json
├── .gitignore
├── index.html
├── global.d.ts
├── vitest.config.ts
├── tsconfig.node.json
├── .eslintrc.cjs
├── uno.config.ts
├── README.md
├── vite.config.ts
├── package.json
└── LICENSE
/src/components/mt-dzr/composables/mouse.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/assets/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yaolunmao/maotu-webtopo/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/src/components/mt-dzr/index.ts:
--------------------------------------------------------------------------------
1 | import MtDzr from './index.vue';
2 |
3 | export default MtDzr;
4 |
--------------------------------------------------------------------------------
/src/components/mt-dzr/utils/types.ts:
--------------------------------------------------------------------------------
1 | export type MouseTouchEvent = MouseEvent | TouchEvent;
2 |
--------------------------------------------------------------------------------
/public/imgs/edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yaolunmao/maotu-webtopo/HEAD/public/imgs/edit.png
--------------------------------------------------------------------------------
/src/components/mt-edit/index.ts:
--------------------------------------------------------------------------------
1 | import MtEdit from './index.vue';
2 |
3 | export default MtEdit;
4 |
--------------------------------------------------------------------------------
/public/imgs/change-attr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yaolunmao/maotu-webtopo/HEAD/public/imgs/change-attr.png
--------------------------------------------------------------------------------
/public/imgs/edit-load.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yaolunmao/maotu-webtopo/HEAD/public/imgs/edit-load.png
--------------------------------------------------------------------------------
/src/components/mt-preview/index.ts:
--------------------------------------------------------------------------------
1 | import MtPreview from './index.vue';
2 |
3 | export default MtPreview;
4 |
--------------------------------------------------------------------------------
/public/imgs/set-node-attr.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yaolunmao/maotu-webtopo/HEAD/public/imgs/set-node-attr.gif
--------------------------------------------------------------------------------
/public/imgs/event-callback.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yaolunmao/maotu-webtopo/HEAD/public/imgs/event-callback.png
--------------------------------------------------------------------------------
/src/assets/imgs/test/my-img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yaolunmao/maotu-webtopo/HEAD/src/assets/imgs/test/my-img.png
--------------------------------------------------------------------------------
/src/components/mt-dzr/components/render-item.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/selected-area/types.ts:
--------------------------------------------------------------------------------
1 | export interface IAreaBinfo {
2 | width: number;
3 | height: number;
4 | top: number;
5 | left: number;
6 | }
7 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "Vue.volar",
4 | "Vue.vscode-typescript-vue-plugin",
5 | "dbaeumer.vscode-eslint",
6 | "esbenp.prettier-vscode"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/prettierrc",
3 | "semi": true,
4 | "tabWidth": 2,
5 | "singleQuote": true,
6 | "printWidth": 100,
7 | "trailingComma": "none"
8 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.codeActionsOnSave": {
3 | "source.fixAll.eslint": "explicit",
4 | "eslint.autoFixOnSave": "explicit"
5 | },
6 | "files.eol": "\n"
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/tsconfig.vitest.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.app.json",
3 | "exclude": [],
4 | "compilerOptions": {
5 | "composite": true,
6 | "lib": [],
7 | "types": ["node", "jsdom"]
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/assets/icons/menu-fold.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/menu-unfold.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/return.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/手车02.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/view-show.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/delete.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | {
5 | "path": "./tsconfig.node.json"
6 | },
7 | {
8 | "path": "./tsconfig.app.json"
9 | },
10 | {
11 | "path": "./tsconfig.vitest.json"
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/src/assets/icons/upload.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/stroke/电动机.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/svgs/my-button.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/svgs/pie-charts.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/full-screen.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
--------------------------------------------------------------------------------
/src/assets/icons/preview.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/export-json.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/import-json.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/vertical-distribution.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.dom.json",
3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
4 | "exclude": ["src/**/__tests__/*"],
5 | "compilerOptions": {
6 | "composite": true,
7 | "baseUrl": ".",
8 | "paths": {
9 | "@/*": ["./src/*"]
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/assets/icons/horizontal-distribution.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/render-core/types.ts:
--------------------------------------------------------------------------------
1 | import type { CacheBoundingBox, IDoneJsonBinfo } from '../../store/types';
2 |
3 | export interface onItemMoveParams {
4 | move_item_bounding_info: MoveItemBoundingInfo[];
5 | move_binfo: IDoneJsonBinfo & { id: string };
6 | }
7 | export type MoveItemBoundingInfo = CacheBoundingBox;
8 |
--------------------------------------------------------------------------------
/src/assets/icons/tree-list.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/thumbnail.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/save.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/view-hide.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/fs/电阻(阻抗).svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | R
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | .DS_Store
12 | dist
13 | dist-ssr
14 | coverage
15 | *.local
16 |
17 | /cypress/videos/
18 | /cypress/screenshots/
19 |
20 | # Editor directories and files
21 | .idea
22 | *.suo
23 | *.ntvs*
24 | *.njsproj
25 | *.sln
26 | *.sw?
27 |
--------------------------------------------------------------------------------
/src/assets/icons/align-left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/align-right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/layout/footer-panel/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Copyright (c) 2023
4 |
5 | 咬轮猫
6 |
7 |
8 |
11 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | MaoTu
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/global.d.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | interface Window {
3 | $svgEventCallBack: (type: string, svg_item_id: string, ...args: any[]) => void;
4 | $setItemAttrByID: (id: string, key: string, val: any) => Promise;
5 | $getItemAttrByID: (id: string, key: string, val: any) => any;
6 | $previewCompareVal: (val1: any, operator: '>' | '<' | '=' | '!=', val2: any) => boolean;
7 | }
8 | }
9 | export {};
10 |
--------------------------------------------------------------------------------
/src/assets/icons/align-vertical.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/mt-dzr/__tests__/mt-dzr.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from 'vitest';
2 |
3 | import { mount } from '@vue/test-utils';
4 | import HelloWorld from '../index.vue';
5 |
6 | describe('HelloWorld', () => {
7 | it('renders properly', () => {
8 | const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } });
9 | expect(wrapper.text()).toContain('Hello Vitest');
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/src/assets/icons/align-bottom.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/align-horizontally.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/align-top.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/add.svg:
--------------------------------------------------------------------------------
1 |
3 |
6 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/stroke/熔断器.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/components/mt-dzr/store/types.ts:
--------------------------------------------------------------------------------
1 | import type { IDzrPropsModelValue } from '../types';
2 |
3 | export interface IDzrStore {
4 | dzr_copy_info: IDzrCopyInfo;
5 | setDzrCopyInfo: (value: IDzrCopyInfo) => void;
6 | showDzrCopy: (value: IDzrPropsModelValue, gen_id: string) => void;
7 | hideDzrCopy: () => void;
8 | }
9 | export interface IDzrCopyInfo {
10 | gen_id: string;
11 | show: boolean;
12 | value: IDzrPropsModelValue;
13 | }
14 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { fileURLToPath } from 'node:url'
2 | import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
3 | import viteConfig from './vite.config'
4 |
5 | export default mergeConfig(
6 | viteConfig,
7 | defineConfig({
8 | test: {
9 | environment: 'jsdom',
10 | exclude: [...configDefaults.exclude, 'e2e/*'],
11 | root: fileURLToPath(new URL('./', import.meta.url))
12 | }
13 | })
14 | )
15 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/node18/tsconfig.json",
3 | "include": [
4 | "vite.config.*",
5 | "vitest.config.*",
6 | "cypress.config.*",
7 | "nightwatch.conf.*",
8 | "playwright.config.*"
9 | ],
10 | "compilerOptions": {
11 | "composite": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Bundler",
14 | "types": ["node"]
15 | },
16 | "paths":{
17 | "@/*":["src/*"]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/assets/icons/help.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/rotate.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/types.ts:
--------------------------------------------------------------------------------
1 | import type { IDoneJson, IGlobalStoreCanvasCfg, IGlobalStoreGridCfg } from '../store/types';
2 |
3 | export type MouseTouchEvent = MouseEvent | TouchEvent;
4 | export interface IExportDoneJson extends Omit {
5 | props: {
6 | [key: string]: any;
7 | };
8 | }
9 | export interface IExportJson {
10 | canvasCfg: IGlobalStoreCanvasCfg;
11 | gridCfg: IGlobalStoreGridCfg;
12 | json: IExportDoneJson[];
13 | }
14 |
--------------------------------------------------------------------------------
/src/assets/icons/redo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/unlock.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/undo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/svg-analysis/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
20 |
--------------------------------------------------------------------------------
/src/export.ts:
--------------------------------------------------------------------------------
1 | import 'element-plus/dist/index.css';
2 | import 'virtual:uno.css';
3 | import '@/assets/css-vars.css';
4 | import 'virtual:svg-icons-register';
5 | import 'animate.css';
6 | import '@/components/mt-edit/assets/css/custom_ani.css';
7 | import MtDzr from '@/components/mt-dzr';
8 | import MtEdit from '@/components/mt-edit';
9 | import MtPreview from '@/components/mt-preview';
10 | import { leftAsideStore } from '@/components/mt-edit/store/left-aside';
11 | export { MtDzr, MtEdit, MtPreview, leftAsideStore };
12 |
--------------------------------------------------------------------------------
/src/assets/icons/lock.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/test/my-button/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ props.text }}
4 |
5 |
6 |
13 |
19 |
--------------------------------------------------------------------------------
/src/assets/icons/exit-full-screen.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/stroke/三绕组自耦变压器.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/stroke/手车01.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/fs/火花间隙.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/components/test/my-input/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
18 |
19 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | require('@rushstack/eslint-patch/modern-module-resolution');
3 |
4 | module.exports = {
5 | root: true,
6 | extends: [
7 | 'plugin:vue/vue3-essential',
8 | 'eslint:recommended',
9 | '@vue/eslint-config-typescript',
10 | '@vue/eslint-config-prettier'
11 | // '@vue/eslint-config-prettier/skip-formatting'
12 | ],
13 | parserOptions: {
14 | ecmaVersion: 'latest'
15 | },
16 | rules: {
17 | 'vue/multi-word-component-names': [
18 | 'error',
19 | {
20 | ignores: ['index', 'main']
21 | }
22 | ]
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/src/assets/icons/align.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/src/assets/icons/search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue';
2 | import App from './App.vue';
3 | import './assets/main.css';
4 | import router from './router/index';
5 | import MyButton from '@/components/test/my-button/index.vue';
6 | import MyInput from '@/components/test/my-input/index.vue';
7 | import CustomDemo from '@/components/test/custom-demo/index.vue';
8 | import PieCharts from '@/components/test/pie-charts/index.vue';
9 | const app = createApp(App);
10 | app.use(router);
11 | app.component('my-input', MyInput);
12 | app.component('my-button', MyButton);
13 | app.component('custom-demo', CustomDemo);
14 | app.component('pie-charts', PieCharts);
15 | app.mount('#app');
16 |
--------------------------------------------------------------------------------
/src/assets/icons/pen-line.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/stroke/电容器.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/stroke/接地.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/fs/故障.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/preview/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
20 |
--------------------------------------------------------------------------------
/public/svgs/my-input.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/uno.config.ts:
--------------------------------------------------------------------------------
1 | // uno.config.ts
2 | import { defineConfig, presetAttributify, presetUno } from 'unocss';
3 |
4 | export default defineConfig({
5 | theme: {
6 | colors: {
7 | // ...
8 | myDarkBgColor: '#141414',
9 | myMainDarkBgColor: '#1c1c1c'
10 | }
11 | },
12 | presets: [
13 | presetAttributify({
14 | /* preset options */
15 | }),
16 | presetUno()
17 | ],
18 | shortcuts: {
19 | 'ct-border': 'border-t-1px border-t-solid border-t-gray-300',
20 | 'cr-border': 'border-r-1px border-r-solid border-r-gray-300 ',
21 | 'cl-border': 'border-l-1px border-l-solid border-l-gray-300',
22 | 'cb-border': 'border-b-1px border-b-solid border-b-gray-300'
23 | }
24 | });
25 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/custom-svg-render/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
27 |
--------------------------------------------------------------------------------
/src/assets/icons/dark.svg:
--------------------------------------------------------------------------------
1 |
4 |
6 |
--------------------------------------------------------------------------------
/src/components/mt-dzr/types.ts:
--------------------------------------------------------------------------------
1 | export interface IDzrProps {
2 | id: string;
3 | modelValue: IDzrPropsModelValue; //位置和大小
4 | scaleRatio?: number; //画布缩放倍数
5 | hide: boolean; //隐藏
6 | grid?: IDzrPropsGrid; //网格配置
7 | resize?: boolean; //开启缩放
8 | rotate?: boolean; //开启旋转
9 | lock?: boolean; //锁定
10 | active?: boolean; //激活
11 | useProportionalScaling?: boolean; //开启等比例缩放
12 | showGhostDom?: boolean; //是否显示幽灵dom
13 | class?: string; //
14 | disabled: boolean; //是否禁用
15 | adsorp_diff?: {
16 | x: number;
17 | y: number;
18 | };
19 | }
20 | export interface IDzrPropsModelValue {
21 | left: number;
22 | top: number;
23 | width: number;
24 | height: number;
25 | angle: number;
26 | }
27 | export interface IDzrPropsGrid {
28 | enabled: boolean; //开启网格
29 | align: boolean; //对齐到网格
30 | size: number; //网格大小
31 | }
32 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/双绕组变压器.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/components/custom-components/text-vue/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 | {{ props.text }}
8 |
9 |
10 |
34 |
40 |
--------------------------------------------------------------------------------
/src/assets/icons/line.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/stroke/电缆终端头.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/mt-dzr/store/index.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from 'vue';
2 | import type { IDzrCopyInfo, IDzrStore } from './types';
3 | import type { IDzrPropsModelValue } from '../types';
4 |
5 | export const dzrStore: IDzrStore = reactive({
6 | dzr_copy_info: {
7 | gen_id: '',
8 | show: false,
9 | value: {
10 | left: 0,
11 | top: 0,
12 | width: 0,
13 | height: 0,
14 | angle: 0
15 | }
16 | },
17 | setDzrCopyInfo: (value: IDzrCopyInfo) => {
18 | dzrStore.dzr_copy_info = value;
19 | },
20 | showDzrCopy: (value: IDzrPropsModelValue, gen_id: string) => {
21 | dzrStore.setDzrCopyInfo({
22 | ...dzrStore.dzr_copy_info,
23 | show: true,
24 | value,
25 | gen_id
26 | });
27 | },
28 | hideDzrCopy: () => {
29 | dzrStore.setDzrCopyInfo({
30 | ...dzrStore.dzr_copy_info,
31 | show: false
32 | });
33 | }
34 | });
35 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/svg-render/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
28 |
--------------------------------------------------------------------------------
/src/assets/icons/ungroup.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/stroke/电抗器.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/fs/避雷器.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/assets/icons/group.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/stroke/隔离开关-刀闸.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/stroke/自动空气断路器.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/components/mt-edit/assets/css/custom_ani.css:
--------------------------------------------------------------------------------
1 | @-webkit-keyframes rotate360 {
2 | 0% {
3 | -webkit-transform: rotate3d(0, 0, 1, 0deg);
4 | transform: rotate3d(0, 0, 1, 0deg);
5 | }
6 | 50% {
7 | -webkit-transform: rotate3d(0, 0, 1, 180deg);
8 | transform: rotate3d(0, 0, 1, 180deg);
9 | }
10 | to {
11 | -webkit-transform: rotate3d(0, 0, 1, 360deg);
12 | transform: rotate3d(0, 0, 1, 360deg);
13 | }
14 | }
15 |
16 | @keyframes rotate360 {
17 | 0% {
18 | -webkit-transform: rotate3d(0, 0, 1, 0deg);
19 | transform: rotate3d(0, 0, 1, 0deg);
20 | }
21 | 50% {
22 | -webkit-transform: rotate3d(0, 0, 1, 180deg);
23 | transform: rotate3d(0, 0, 1, 180deg);
24 | }
25 | to {
26 | -webkit-transform: rotate3d(0, 0, 1, 360deg);
27 | transform: rotate3d(0, 0, 1, 360deg);
28 | }
29 | }
30 |
31 | .animate__rotate360 {
32 | -webkit-animation-name: rotate360;
33 | animation-name: rotate360;
34 | animation-timing-function: linear;
35 | -webkit-transform-origin: center;
36 | transform-origin: center;
37 | }
38 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/stroke/断路器-开关.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/components/custom-components/card-vue/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
30 |
40 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/交流发电机.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | G
5 | ~
6 |
--------------------------------------------------------------------------------
/src/assets/icons/question.svg:
--------------------------------------------------------------------------------
1 |
3 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/components/mt-edit/store/cache.ts:
--------------------------------------------------------------------------------
1 | import { nextTick, reactive } from 'vue';
2 | import type { CacheBoundingBox, ICache, IDoneJson } from './types';
3 | import { objectDeepClone } from '../utils';
4 |
5 | export const cacheStore: ICache = reactive({
6 | boundingBox: [],
7 | setBoundingBox: (val: CacheBoundingBox[]) => {
8 | cacheStore.boundingBox = val;
9 | },
10 | adsorbPoint: [],
11 | setAdsorbPoint(val) {
12 | cacheStore.adsorbPoint = val;
13 | },
14 | copy: [],
15 | setCopy(val) {
16 | cacheStore.copy = val;
17 | },
18 | history: [[]],
19 | historyIndex: 0,
20 | addHistory(val: IDoneJson[]) {
21 | nextTick(() => {
22 | if (cacheStore.historyIndex + 1 < cacheStore.history.length) {
23 | cacheStore.history.splice(cacheStore.historyIndex + 1);
24 | }
25 | cacheStore.history.push(objectDeepClone(val));
26 | cacheStore.historyIndex = cacheStore.history.length - 1;
27 | if (cacheStore.history.length > 20) {
28 | cacheStore.history.shift();
29 | cacheStore.historyIndex = cacheStore.history.length - 1;
30 | }
31 | });
32 | }
33 | });
34 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/stroke/跌落式熔断器.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/views/edit/index.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
27 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/三绕组变压器.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/svgs/electrical/stroke/消弧线圈.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/export-json/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 |
41 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/import-json/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
17 |
43 |
--------------------------------------------------------------------------------
/src/assets/icons/setting.svg:
--------------------------------------------------------------------------------
1 |
3 |
6 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory } from 'vue-router';
2 | export const constantRoutes = [
3 | {
4 | path: '/',
5 | component: () => import('../views/edit/index.vue')
6 | },
7 | {
8 | name: 'edit',
9 | path: '/edit',
10 | component: () => import('../views/edit/index.vue')
11 | },
12 | {
13 | name: 'demo',
14 | path: '/demo',
15 | component: () => import('../views/demo/index.vue')
16 | },
17 | {
18 | name: 'preview',
19 | path: '/preview',
20 | component: () => import('../views/preview/index.vue')
21 | },
22 | {
23 | name: 'edit-load',
24 | path: '/edit-load',
25 | component: () => import('../views/demo/edit-load.vue')
26 | },
27 | {
28 | name: 'set-node-attr',
29 | path: '/set-node-attr',
30 | component: () => import('../views/demo/set-node-attr.vue')
31 | },
32 | {
33 | name: 'event-callback',
34 | path: '/event-callback',
35 | component: () => import('../views/demo/event-callback.vue')
36 | },
37 | {
38 | name: 'change-attr',
39 | path: '/change-attr',
40 | component: () => import('../views/demo/change-attr.vue')
41 | }
42 | ];
43 | const router = createRouter({
44 | history: createWebHistory(), // hash模式:createWebHashHistory,history模式:createWebHistory
45 | routes: constantRoutes
46 | });
47 |
48 | export default router;
49 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/layout/right-aside/select-item-event-setting/input-target-value.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
13 |
14 |
15 |
16 |
32 |
--------------------------------------------------------------------------------
/src/components/custom-components/sys-button-vue/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ props.text }}
5 |
6 |
7 |
8 |
49 |
--------------------------------------------------------------------------------
/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | ao
31 | tu
32 |
33 | M A O T U
34 |
35 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/layout/right-aside/json-edit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 点击编辑
4 |
5 |
17 |
18 |
22 |
23 |
24 |
25 |
26 |
44 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/group-render/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
41 |
49 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/layout/right-aside/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
36 |
42 |
--------------------------------------------------------------------------------
/src/components/mt-edit/store/left-aside.ts:
--------------------------------------------------------------------------------
1 | import { getCurrentInstance, reactive } from 'vue';
2 | import type { ILeftAside, ILeftAsideConfigItemPublic, ILeftAsideConfigItem } from './types';
3 | import { ElMessage } from 'element-plus';
4 | import { svgToSymbol } from '../utils';
5 | import { configStore } from './config';
6 |
7 | export const leftAsideStore: ILeftAside = reactive({
8 | config: new Map([
9 | ['本地文件', []],
10 | ['系统组件', configStore.sysComponent]
11 | ]),
12 | registerConfig: (title: string, config: ILeftAsideConfigItemPublic[]) => {
13 | if (title == '本地文件' || title == '系统组件') {
14 | ElMessage.info(`title:${title}已被系统占用,请更换名称!`);
15 | return;
16 | }
17 |
18 | if (leftAsideStore.config.has(title)) {
19 | ElMessage.info(`title:${title}已存在,已经将其配置覆盖`);
20 | }
21 | const cfg: ILeftAsideConfigItem[] = config.map((m) => {
22 | if (m.type == 'svg') {
23 | const { symbol_str, width, height } = svgToSymbol(m.svg!, m.id);
24 | return {
25 | ...m,
26 | symbol: {
27 | symbol_id: m.id,
28 | symbol_str,
29 | width,
30 | height
31 | },
32 | common_animations: {
33 | val: '',
34 | delay: 'delay-0s',
35 | speed: 'slow',
36 | repeat: 'infinite'
37 | }
38 | };
39 | }
40 | return {
41 | ...m,
42 | common_animations: {
43 | val: '',
44 | delay: 'delay-0s',
45 | speed: 'slow',
46 | repeat: 'infinite'
47 | }
48 | };
49 | });
50 | leftAsideStore.config.set(title, cfg);
51 | }
52 | });
53 |
--------------------------------------------------------------------------------
/src/components/custom-components/kv-vue/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ props.label }}
6 | {{ props.value }}
7 |
8 |
9 |
10 |
11 |
53 |
72 |
--------------------------------------------------------------------------------
/src/views/demo/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
插件能力展示
4 |
5 |
11 |
12 | {{ item.title }}
13 |
14 |
15 |
16 |
17 |
18 |
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # maotu-webtopo
2 |
3 | 基于 vue3 的 web 组态引擎库
4 |
5 | 探索将已有 svg 文件转为自由缩放图形库的解决方案,也可用作低代码大屏项目开发
6 |
7 | ## 说明
8 |
9 | 此开源版本的代码源自于 [maotu 插件版](https://www.npmjs.com/package/maotu) 0.3.1 版本,与插件版的差异请查阅插件版的 readme。
10 |
11 | ## 使用文档
12 |
13 | 请参考:[http://mt.yaolm.top](http://mt.yaolm.top)
14 |
15 | ## 声明
16 |
17 | `maotu-webtopo` 使用了 `LGPL-3.0` 协议。这意味着:
18 | * 您可以将 `maotu-webtopo` 作为库链接到您的商业项目,而无需开源您的整个项目。
19 | * 如果您修改了 `maotu-webtopo` 的**核心库**代码,并分发了修改后的版本,您必须按照 LGPL-3.0 协议的要求,开源您所做的修改。
20 | * 如果您仅仅是将`maotu-webtopo` 作为库链接到你的项目,而没有修改或分发它的源代码,那么你的项目无需开源。
21 |
22 | 详细的 LGPL-3.0 许可证文本请查阅 [https://www.gnu.org/licenses/lgpl-3.0.html](https://www.gnu.org/licenses/lgpl-3.0.html)。
23 |
24 | ## 如何构建插件并使用
25 |
26 | **构建库:**
27 |
28 | 1. 使用 `pnpm run lib` 命令构建 `maotu-webtopo`,生成 `dist` 文件夹。
29 |
30 | **使用库:**
31 |
32 | 1. **推荐使用 pnpm 安装:**
33 |
34 | ```bash
35 | pnpm i maotu # 前提是你已经发布到了 npm
36 | ```
37 |
38 | 如果选择手动复制,请继续参考以下步骤
39 |
40 | 2. 将 `dist` 文件夹中的以下文件复制到你的项目:
41 | * `dist/maotu.es.js`:库的入口文件。
42 | * `dist/style.css`:库的样式文件。
43 | * `dist/src` : 库的ts类型定义。
44 | * 将这些文件放到你项目中的合适位置。例如,你可以创建一个 `src/lib/maotu` 目录,并将它们复制到这里。
45 |
46 | 3. 确保你的项目可以访问到 `style.css` 文件。可以通过在入口文件或组件中引入的方式来实现。
47 |
48 | **在项目中使用示例:**
49 |
50 | ```vue
51 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | ```
64 |
65 | ## 鸣谢
66 |
67 | maotu的部分逻辑实现参考了以下大佬的文章
68 |
69 | [幽月之格-可拖拽、缩放、旋转组件实现细节](https://juejin.cn/user/3597257779449165/posts)
70 |
71 | [woai3c-一个低代码(可视化拖拽)教学项目](https://github.com/woai3c/visual-drag-demo)
72 |
73 |
--------------------------------------------------------------------------------
/src/components/test/pie-charts/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
65 |
66 |
72 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/drag-canvas/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
49 |
50 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/done-tree/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
{{ node.label }}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
52 |
--------------------------------------------------------------------------------
/src/components/mt-edit/store/context-menu.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from 'vue';
2 | import type { ContextMenuInfoType, IContextMenu } from './types';
3 |
4 | export const contextMenuStore: IContextMenu = reactive({
5 | menuInfo: {
6 | display: false,
7 | left: 0,
8 | top: 0,
9 | info: {
10 | selectAll: {
11 | title: '全选',
12 | hot_key: 'Ctrl + A',
13 | enable: false
14 | },
15 | copy: {
16 | title: '复制',
17 | hot_key: 'Ctrl + C',
18 | enable: false
19 | },
20 | paste: {
21 | title: '粘贴',
22 | hot_key: 'Ctrl + V',
23 | enable: false
24 | },
25 | delete: {
26 | title: '删除',
27 | hot_key: 'Delete',
28 | enable: false
29 | },
30 | group: {
31 | title: '组合',
32 | hot_key: 'Ctrl + G',
33 | enable: false
34 | },
35 | ungroup: {
36 | title: '取消组合',
37 | hot_key: 'Ctrl + U',
38 | enable: false
39 | },
40 | moveTop: {
41 | title: '置顶',
42 | hot_key: 'Ctrl + Right',
43 | enable: false
44 | },
45 | moveUp: {
46 | title: '上移',
47 | hot_key: 'Ctrl + Up',
48 | enable: false
49 | },
50 | moveDown: {
51 | title: '下移',
52 | hot_key: 'Ctrl + Down',
53 | enable: false
54 | },
55 | moveBottom: {
56 | title: '置底',
57 | hot_key: 'Ctrl + Left',
58 | enable: false
59 | }
60 | }
61 | },
62 | setMenuInfo: (val: IContextMenu['menuInfo']) => {
63 | contextMenuStore.menuInfo = val;
64 | },
65 | setDisplayItem: (val: ContextMenuInfoType[]) => {
66 | for (const key in contextMenuStore.menuInfo.info) {
67 | contextMenuStore.menuInfo.info[key as ContextMenuInfoType].enable = false;
68 | }
69 | val.forEach((f) => {
70 | contextMenuStore.menuInfo.info[f].enable = true;
71 | });
72 | }
73 | });
74 |
--------------------------------------------------------------------------------
/src/views/demo/set-node-attr.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 改成红色
5 | 改成绿色
6 |
7 |
8 |
9 |
84 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from 'node:url';
2 |
3 | import { defineConfig, UserConfigExport } from 'vite';
4 | import vue from '@vitejs/plugin-vue';
5 | import { resolve } from 'path';
6 | import dts from 'vite-plugin-dts';
7 | import UnoCSS from 'unocss/vite';
8 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
9 | // https://vitejs.dev/config/
10 | export default defineConfig(({ mode }) => {
11 | let config: UserConfigExport = {
12 | plugins: [
13 | vue(),
14 | dts({
15 | tsconfigPath: 'tsconfig.app.json'
16 | }),
17 | UnoCSS({
18 | // 在低版本浏览器上开发时会报错 Unexpected reserved word
19 | // 如果在开发环境需要兼容不支持顶级await的低版本浏览器例如Chrome(v87),就将下面的配置打开
20 | // hmrTopLevelAwait: false
21 | }),
22 | createSvgIconsPlugin({
23 | // 指定需要缓存的图标文件夹
24 | iconDirs: [resolve(process.cwd(), 'src/assets/icons')],
25 | // 指定symbolId格式
26 | symbolId: 'mt-edit-[name]',
27 | // 禁用压缩 否则想要修改无状态组件的stroke或者fill会影响到预设样式 例如stroke-width
28 | svgoOptions: false,
29 | customDomId: '___mt__edit__icons__dom__'
30 | })
31 | ],
32 |
33 | resolve: {
34 | alias: {
35 | '@': fileURLToPath(new URL('./src', import.meta.url))
36 | },
37 | dedupe: ['vue']
38 | }
39 | };
40 | if (mode === 'lib' || mode === 'npm') {
41 | config = {
42 | ...config,
43 | ...{
44 | build: {
45 | lib: {
46 | entry: resolve(__dirname, 'src/export.ts'),
47 | name: 'maotu',
48 | fileName: (format: any) => `maotu.${format}.js`
49 | },
50 | rollupOptions: {
51 | // 确保外部化处理那些你不想打包进库的依赖
52 | external: ['vue', 'pinia'],
53 | output: {
54 | // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
55 | globals: {
56 | vue: 'Vue',
57 | pinia: 'Pinia'
58 | },
59 | inlineDynamicImports: true
60 | }
61 | }
62 | }
63 | }
64 | };
65 | }
66 | return config;
67 | });
68 |
--------------------------------------------------------------------------------
/public/svgs/demo.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
12 |
16 |
20 |
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "maotu",
3 | "version": "0.3.1",
4 | "private": false,
5 | "scripts": {
6 | "dev": "pnpm run format && vite",
7 | "build": "run-p type-check \"build-only {@}\" --",
8 | "preview": "vite preview",
9 | "test:unit": "vitest",
10 | "build-only": "vite build",
11 | "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
12 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
13 | "format": "prettier --write src/",
14 | "lib": "vite build --mode lib",
15 | "npm": "vite build --mode npm"
16 | },
17 | "files": [
18 | "dist"
19 | ],
20 | "main": "./dist/maotu.umd.js",
21 | "module": "./dist/maotu.es.js",
22 | "exports": {
23 | ".": {
24 | "import": "./dist/maotu.es.js",
25 | "require": "./dist/maotu.umd.js",
26 | "types": "./dist/src/export.d.ts"
27 | },
28 | "./*": "./*"
29 | },
30 | "typings": "dist/export.d.ts",
31 | "dependencies": {
32 | "@vueuse/core": "^10.6.1",
33 | "ace-builds": "^1.32.0",
34 | "animate.css": "^4.1.1",
35 | "canvg": "^4.0.1",
36 | "echarts": "^5.4.3",
37 | "element-plus": "^2.4.2",
38 | "html2canvas": "^1.4.1",
39 | "less": "^4.2.0",
40 | "vue": "^3.3.4",
41 | "vue-echarts": "^6.6.5",
42 | "vue-router": "^4.2.5",
43 | "vue3-ace-editor": "^2.2.4"
44 | },
45 | "devDependencies": {
46 | "@rushstack/eslint-patch": "^1.3.3",
47 | "@tsconfig/node18": "^18.2.2",
48 | "@types/jsdom": "^21.1.3",
49 | "@types/node": "^18.18.5",
50 | "@vitejs/plugin-vue": "^4.4.0",
51 | "@vue/eslint-config-prettier": "^8.0.0",
52 | "@vue/eslint-config-typescript": "^12.0.0",
53 | "@vue/test-utils": "^2.4.1",
54 | "@vue/tsconfig": "^0.4.0",
55 | "eslint": "^8.49.0",
56 | "eslint-plugin-vue": "^9.17.0",
57 | "jsdom": "^22.1.0",
58 | "npm-run-all2": "^6.1.1",
59 | "prettier": "^3.0.3",
60 | "typescript": "~5.2.0",
61 | "unocss": "^0.57.4",
62 | "vite": "^4.4.11",
63 | "vite-plugin-dts": "^3.6.0",
64 | "vite-plugin-svg-icons": "^2.0.1",
65 | "vitest": "^0.34.6",
66 | "vue-tsc": "^1.8.19"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/components/mt-edit/composables/index.ts:
--------------------------------------------------------------------------------
1 | import type { IExportDoneJson, IExportJson } from '../components/types';
2 | import { leftAsideStore } from '../store/left-aside';
3 | import type {
4 | IDoneJson,
5 | IGlobalStoreCanvasCfg,
6 | IGlobalStoreGridCfg,
7 | ILeftAsideConfigItem,
8 | ILeftAsideConfigItemPublicProps
9 | } from '../store/types';
10 | import { objectDeepClone } from '../utils';
11 |
12 | export const genExportJson = (
13 | canvasCfg: IGlobalStoreCanvasCfg,
14 | gridCfg: IGlobalStoreGridCfg,
15 | doneJson: IDoneJson[]
16 | ) => {
17 | let export_done_json: IExportDoneJson[] = [];
18 | export_done_json = objectDeepClone(doneJson).map((m) => {
19 | if (m.symbol) {
20 | delete m.symbol;
21 | }
22 | let new_props = {};
23 | for (const key in m.props) {
24 | new_props = { ...new_props, ...{ [key]: m.props[key].val } };
25 | }
26 | return {
27 | ...m,
28 | props: new_props,
29 | active: false
30 | };
31 | });
32 | const exportJson: IExportJson = {
33 | canvasCfg,
34 | gridCfg,
35 | json: export_done_json
36 | };
37 | return { exportJson };
38 | };
39 | export const useExportJsonToDoneJson = (json: IExportJson) => {
40 | // 取出所有图形的初始配置
41 | let init_configs: ILeftAsideConfigItem[] = [];
42 | for (const iterator of leftAsideStore.config.values()) {
43 | if (iterator.length > 0) {
44 | init_configs = [...init_configs, ...iterator];
45 | }
46 | }
47 | const importDoneJson: IDoneJson[] = json.json.map((m) => {
48 | let props: ILeftAsideConfigItemPublicProps = {};
49 | let symbol = undefined;
50 | // 找到原始的props
51 | const find_item = init_configs.find((f) => f?.id == m.tag);
52 | const find_props = find_item?.props;
53 | if (find_props) {
54 | props = { ...props, ...objectDeepClone(find_props) };
55 | }
56 | for (const key in m.props) {
57 | props[key].val = m.props[key];
58 | }
59 | if (find_item?.symbol) {
60 | symbol = find_item.symbol;
61 | }
62 | return {
63 | ...m,
64 | props,
65 | symbol
66 | };
67 | });
68 | return {
69 | canvasCfg: json.canvasCfg,
70 | gridCfg: json.gridCfg,
71 | importDoneJson
72 | };
73 | };
74 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/context-menu/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
31 |
87 |
--------------------------------------------------------------------------------
/src/assets/icons/light.svg:
--------------------------------------------------------------------------------
1 |
4 |
7 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/pattern-grid/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
18 |
19 |
20 |
21 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
74 |
75 |
83 |
--------------------------------------------------------------------------------
/src/components/custom-components/now-time-vue/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ date }}
7 |
8 |
12 | {{ week }}
13 |
14 |
15 |
19 | {{ time }}
20 |
21 |
22 |
23 |
79 |
--------------------------------------------------------------------------------
/src/components/mt-edit/composables/thumbnail.ts:
--------------------------------------------------------------------------------
1 | import { Canvg } from 'canvg';
2 | import html2canvas from 'html2canvas';
3 | import { ElMessage } from 'element-plus';
4 | export const useGenThumbnail = async (canvas_id: string = 'mtCanvasArea') => {
5 | const el = document.querySelector(`#${canvas_id}`);
6 | if (!el) {
7 | ElMessage.error('没有找到canvas元素,请检查!');
8 | return;
9 | }
10 | //记录要移除的svg元素
11 | const shouldRemoveSvgNodes = [];
12 | // 获取到所有的SVG 得到一个数组 目前只有自定义连线需要特殊处理 别的元素直接使用html2canvas就可以
13 | const svgElements: NodeListOf = document.body.querySelectorAll(
14 | `#${canvas_id} .mt-line-render`
15 | );
16 | // 遍历这个数组
17 | for (const item of svgElements) {
18 | //去除空白字符
19 | const svg = item.outerHTML.trim();
20 | // 创建一个 canvas DOM元素
21 | const canvas = document.createElement('canvas');
22 | //设置 canvas 元素的宽高
23 | canvas.width = item.getBoundingClientRect().width;
24 | canvas.height = item.getBoundingClientRect().height;
25 | const ctx = canvas.getContext('2d');
26 | // 将 SVG转化 成 canvas
27 | const v = Canvg.fromString(ctx!, svg);
28 | await v.render();
29 |
30 | //设置生成 canvas 元素的坐标 保证与原SVG坐标保持一致
31 | if (item.style.position) {
32 | canvas.style.position += item.style.position;
33 | canvas.style.left += item.style.left;
34 | canvas.style.top += item.style.top;
35 | }
36 |
37 | //添加到需要截图的DOM节点中
38 | item.parentNode!.appendChild(canvas);
39 | // 删除这个元素
40 | shouldRemoveSvgNodes.push(canvas);
41 | }
42 |
43 | const width = el.offsetWidth;
44 | const height = el.offsetHeight;
45 | const canvas = await html2canvas(el, {
46 | useCORS: true,
47 | scale: 2,
48 | width,
49 | height,
50 | allowTaint: true,
51 | windowHeight: height,
52 | logging: false,
53 | ignoreElements: (element) => {
54 | if (element.classList.contains('mt-line-render')) {
55 | return true;
56 | }
57 | return false;
58 | }
59 | });
60 | const img_link = document.createElement('a');
61 | img_link.href = canvas.toDataURL('image/png'); // 转换后的图片地址
62 | img_link.download = Date.now().toString();
63 | document.body.appendChild(img_link);
64 | // 触发点击
65 | img_link.click();
66 | // 然后移除
67 | document.body.removeChild(img_link);
68 | // 移除需要移除掉的svg节点
69 | shouldRemoveSvgNodes.forEach((item) => {
70 | item.remove();
71 | });
72 | };
73 |
--------------------------------------------------------------------------------
/src/components/test/custom-demo/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
20 |
26 |
31 |
32 |
33 |
34 |
44 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/layout/right-aside/select-item-props-setting.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
17 |
18 |
23 |
28 |
34 |
39 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
78 |
--------------------------------------------------------------------------------
/src/components/mt-edit/ace-edit.ts:
--------------------------------------------------------------------------------
1 | import ace from 'ace-builds';
2 |
3 | import themeMonokaiUrl from 'ace-builds/src-noconflict/theme-monokai?url';
4 | ace.config.setModuleUrl('ace/theme/monokai', themeMonokaiUrl);
5 |
6 | import workerBaseUrl from 'ace-builds/src-noconflict/worker-base?url';
7 | ace.config.setModuleUrl('ace/mode/base', workerBaseUrl);
8 |
9 | import modeJsonUrl from 'ace-builds/src-noconflict/mode-json?url';
10 | ace.config.setModuleUrl('ace/mode/json', modeJsonUrl);
11 | import workerJsonUrl from 'ace-builds/src-noconflict/worker-json?url';
12 | ace.config.setModuleUrl('ace/mode/json_worker', workerJsonUrl);
13 | import snippetsJsonUrl from 'ace-builds/src-noconflict/snippets/json?url';
14 | ace.config.setModuleUrl('ace/snippets/json', snippetsJsonUrl);
15 |
16 | import modeJavascriptUrl from 'ace-builds/src-noconflict/mode-javascript?url';
17 | ace.config.setModuleUrl('ace/mode/javascript', modeJavascriptUrl);
18 | import workerJavascriptUrl from 'ace-builds/src-noconflict/worker-javascript?url';
19 | ace.config.setModuleUrl('ace/mode/javascript_worker', workerJavascriptUrl);
20 | import snippetsJavascriptUrl from 'ace-builds/src-noconflict/snippets/javascript?url';
21 | ace.config.setModuleUrl('ace/snippets/javascript', snippetsJavascriptUrl);
22 |
23 | import 'ace-builds/src-noconflict/ext-language_tools';
24 | // ace.require('ace/ext/language_tools');
25 | const langTools = ace.require('ace/ext/language_tools');
26 | langTools.addCompleter({
27 | getCompletions: function (
28 | _editor: any,
29 | _session: any,
30 | _pos: any,
31 | prefix: string | any[],
32 | callback: (
33 | arg0: null,
34 | arg1: {
35 | name: string; //显示的名称
36 | value: string; //插入的值,
37 | score: number; //分数
38 | meta: string; //描述
39 | }[]
40 | ) => void
41 | ) {
42 | if (prefix.length === 0) {
43 | callback(null, []);
44 | return;
45 | }
46 | callback(null, [
47 | {
48 | name: '$mtEventCallBack',
49 | value: '$mtEventCallBack(type,$item_info.id)',
50 | score: 1000,
51 | meta: '执行订阅回调函数'
52 | },
53 | {
54 | name: '$mtElMessage',
55 | value: '$mtElMessage.success("成功")',
56 | score: 1000,
57 | meta: '消息提示'
58 | },
59 | {
60 | name: '$mtElMessageBox',
61 | value: `$mtElMessageBox.alert('This is a message', 'Title', {
62 | confirmButtonText: 'OK',
63 | callback: (action) => {
64 | console.log(action)
65 | },
66 | })`,
67 | score: 1000,
68 | meta: '消息弹出框'
69 | },
70 | {
71 | name: '$item_info',
72 | value: '$item_info',
73 | score: 1000,
74 | meta: '回调函数中获取当前触发事件图形的信息'
75 | }
76 | ]);
77 | }
78 | });
79 |
--------------------------------------------------------------------------------
/src/components/mt-edit/store/global.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from 'vue';
2 | import type {
3 | GlobalStoreIntention,
4 | IDoneJson,
5 | IGlobalStore,
6 | IGlobalStoreCreateItemInfo,
7 | IRealTimeData
8 | } from './types';
9 | export const globalStore: IGlobalStore = reactive({
10 | intention: 'none',
11 | create_item_info: null,
12 | selected_items_id: [],
13 | done_json: [],
14 | canvasCfg: {
15 | width: 1920,
16 | height: 1080,
17 | scale: 1,
18 | color: '',
19 | img: '',
20 | guide: true,
21 | adsorp: true,
22 | adsorp_diff: 5,
23 | transform_origin: {
24 | x: 0,
25 | y: 0
26 | },
27 | drag_offset: {
28 | x: 0,
29 | y: 0
30 | }
31 | },
32 | gridCfg: {
33 | enabled: true,
34 | align: true,
35 | size: 10
36 | },
37 | guideCfg: {
38 | x: {
39 | display: false,
40 | top: 0
41 | },
42 | y: {
43 | display: false,
44 | left: 0
45 | }
46 | },
47 | lock: false,
48 | real_time_data: {
49 | show: false,
50 | text: ''
51 | },
52 | adsorp_diff: {
53 | x: 0,
54 | y: 0
55 | },
56 | setIntention: (val: GlobalStoreIntention) => {
57 | globalStore.intention = val;
58 | },
59 | setCreateItemInfo: (val: IGlobalStoreCreateItemInfo | null) => {
60 | globalStore.create_item_info = val;
61 | },
62 | setGlobalStoreDoneJson: (val: IDoneJson[]) => {
63 | globalStore.done_json = val;
64 | },
65 | //取消所有组件选中
66 | cancelAllSelect: () => {
67 | const done_json_temp = [...globalStore.done_json].map((m) => {
68 | if (m.active) {
69 | m.active = false;
70 | }
71 | return m;
72 | });
73 | globalStore.setGlobalStoreDoneJson(done_json_temp);
74 | globalStore.selected_items_id = [];
75 | },
76 | //刷新选中的id
77 | refreshSelectedItemsId: () => {
78 | globalStore.selected_items_id = globalStore.done_json.filter((m) => m.active).map((m) => m.id);
79 | },
80 | //删除选中的组件
81 | deleteSelectedItems: () => {
82 | const done_json_temp = [...globalStore.done_json].filter(
83 | (m) => !globalStore.selected_items_id.includes(m.id)
84 | );
85 | globalStore.setGlobalStoreDoneJson(done_json_temp);
86 | globalStore.selected_items_id = [];
87 | },
88 | // 设置单个选中
89 | setSingleSelect: (id: string) => {
90 | globalStore.done_json.forEach((m) => {
91 | if (m.id === id) {
92 | m.active = true;
93 | } else {
94 | m.active = false;
95 | }
96 | });
97 | globalStore.selected_items_id = [id];
98 | },
99 | setSelectItems: (ids: string[]) => {
100 | globalStore.done_json.forEach((m) => {
101 | if (ids.includes(m.id)) {
102 | m.active = true;
103 | } else {
104 | m.active = false;
105 | }
106 | });
107 | globalStore.selected_items_id = ids;
108 | },
109 | setRealTimeData: (val: IRealTimeData) => {
110 | globalStore.real_time_data = val;
111 | }
112 | });
113 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/selected-area/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
83 |
95 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/layout/right-aside/select-item-animate-setting/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
92 |
--------------------------------------------------------------------------------
/src/views/demo/event-callback.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
129 |
130 |
131 |
--------------------------------------------------------------------------------
/src/assets/css-vars.css:
--------------------------------------------------------------------------------
1 | #mt-edit.dark {
2 | color-scheme: dark;
3 | --el-color-primary: #409eff;
4 | --el-color-primary-light-3: #3375b9;
5 | --el-color-primary-light-5: #2a598a;
6 | --el-color-primary-light-7: #213d5b;
7 | --el-color-primary-light-8: #1d3043;
8 | --el-color-primary-light-9: #18222c;
9 | --el-color-primary-dark-2: #66b1ff;
10 | --el-color-success: #67c23a;
11 | --el-color-success-light-3: #4e8e2f;
12 | --el-color-success-light-5: #3e6b27;
13 | --el-color-success-light-7: #2d481f;
14 | --el-color-success-light-8: #25371c;
15 | --el-color-success-light-9: #1c2518;
16 | --el-color-success-dark-2: #85ce61;
17 | --el-color-warning: #e6a23c;
18 | --el-color-warning-light-3: #a77730;
19 | --el-color-warning-light-5: #7d5b28;
20 | --el-color-warning-light-7: #533f20;
21 | --el-color-warning-light-8: #3e301c;
22 | --el-color-warning-light-9: #292218;
23 | --el-color-warning-dark-2: #ebb563;
24 | --el-color-danger: #f56c6c;
25 | --el-color-danger-light-3: #b25252;
26 | --el-color-danger-light-5: #854040;
27 | --el-color-danger-light-7: #582e2e;
28 | --el-color-danger-light-8: #412626;
29 | --el-color-danger-light-9: #2b1d1d;
30 | --el-color-danger-dark-2: #f78989;
31 | --el-color-error: #f56c6c;
32 | --el-color-error-light-3: #b25252;
33 | --el-color-error-light-5: #854040;
34 | --el-color-error-light-7: #582e2e;
35 | --el-color-error-light-8: #412626;
36 | --el-color-error-light-9: #2b1d1d;
37 | --el-color-error-dark-2: #f78989;
38 | --el-color-info: #909399;
39 | --el-color-info-light-3: #6b6d71;
40 | --el-color-info-light-5: #525457;
41 | --el-color-info-light-7: #393a3c;
42 | --el-color-info-light-8: #2d2d2f;
43 | --el-color-info-light-9: #202121;
44 | --el-color-info-dark-2: #a6a9ad;
45 | --el-box-shadow: 0px 12px 32px 4px rgba(0, 0, 0, 0.36), 0px 8px 20px rgba(0, 0, 0, 0.72);
46 | --el-box-shadow-light: 0px 0px 12px rgba(0, 0, 0, 0.72);
47 | --el-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, 0.72);
48 | --el-box-shadow-dark: 0px 16px 48px 16px rgba(0, 0, 0, 0.72), 0px 12px 32px #000000,
49 | 0px 8px 16px -8px #000000;
50 | --el-bg-color-page: #0a0a0a;
51 | --el-bg-color: #141414;
52 | --el-bg-color-overlay: #1d1e1f;
53 | --el-text-color-primary: #e5eaf3;
54 | --el-text-color-regular: #cfd3dc;
55 | --el-text-color-secondary: #a3a6ad;
56 | --el-text-color-placeholder: #8d9095;
57 | --el-text-color-disabled: #6c6e72;
58 | --el-border-color-darker: #636466;
59 | --el-border-color-dark: #58585b;
60 | --el-border-color: #4c4d4f;
61 | --el-border-color-light: #414243;
62 | --el-border-color-lighter: #363637;
63 | --el-border-color-extra-light: #2b2b2c;
64 | --el-fill-color-darker: #424243;
65 | --el-fill-color-dark: #39393a;
66 | --el-fill-color: #303030;
67 | --el-fill-color-light: #262727;
68 | --el-fill-color-lighter: #1d1d1d;
69 | --el-fill-color-extra-light: #191919;
70 | --el-fill-color-blank: transparent;
71 | --el-mask-color: rgba(0, 0, 0, 0.8);
72 | --el-mask-color-extra-light: rgba(0, 0, 0, 0.3);
73 | transition: all 5s;
74 | }
75 | #mt-edit.dark .el-button {
76 | --el-button-disabled-text-color: rgba(255, 255, 255, 0.5);
77 | }
78 | #mt-edit.dark .el-card {
79 | --el-card-bg-color: var(--el-bg-color-overlay);
80 | }
81 | #mt-edit.dark .el-empty {
82 | --el-empty-fill-color-0: var(--el-color-black);
83 | --el-empty-fill-color-1: #4b4b52;
84 | --el-empty-fill-color-2: #36383d;
85 | --el-empty-fill-color-3: #1e1e20;
86 | --el-empty-fill-color-4: #262629;
87 | --el-empty-fill-color-5: #202124;
88 | --el-empty-fill-color-6: #212224;
89 | --el-empty-fill-color-7: #1b1c1f;
90 | --el-empty-fill-color-8: #1c1d1f;
91 | --el-empty-fill-color-9: #18181a;
92 | }
93 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/render-item/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
19 |
onUpdateModelValue(item_json.props, val)"
25 | >
26 |
32 |
33 |
38 |
39 |
emits('setIntention', val)"
49 | @line-mouse-up="emits('lineMouseUp')"
50 | >
51 |
52 |
53 |
101 |
102 |
--------------------------------------------------------------------------------
/src/components/mt-dzr/components/rotate-handle.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
77 |
89 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
--------------------------------------------------------------------------------
/src/views/demo/change-attr.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
221 |
222 |
223 |
--------------------------------------------------------------------------------
/src/components/mt-preview/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
15 |
25 |
26 |
33 |
34 |
35 |
36 |
172 |
183 |
--------------------------------------------------------------------------------
/src/components/mt-dzr/components/resize-handle.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
onMouseDown(e, key)"
10 | @touchstart.passive="(e) => onMouseDown(e, key)"
11 | >
12 |
13 |
14 |
180 |
197 |
--------------------------------------------------------------------------------
/src/views/demo/edit-load.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
258 |
259 |
260 |
--------------------------------------------------------------------------------
/src/components/mt-edit/store/types.ts:
--------------------------------------------------------------------------------
1 | export type ILeftAsideConfig = Map;
2 | export type ILeftAsideConfigItemPublicPropsType =
3 | | 'input'
4 | | 'color'
5 | | 'select'
6 | | 'switch'
7 | | 'number'
8 | | 'jsonEdit'
9 | | 'textArea';
10 | // 开放注册配置
11 | export type ILeftAsideConfigItemPublicProps = Record<
12 | string,
13 | {
14 | title: string; //显示在属性面板的标题
15 | type: ILeftAsideConfigItemPublicPropsType; //属性的类型决定了修改属性的方式
16 | val: any;
17 | options?: any; //比如说修改属性的时候用到了下拉框,这里面就可以放下拉框的选项
18 | disabled?: boolean; //如果禁用了将不会显示到右侧属性面板里,但是仍然可以通过代码修改属性
19 | }
20 | >;
21 | export type ILeftAsideConfigItemPublicType = 'svg' | 'vue' | 'img' | 'custom-svg';
22 | export type ILeftAsideConfigItemPrivateType = 'group' | 'sys-line';
23 | export interface ILeftAsideConfigItemPublic {
24 | id: string; //图形的标识 值必须唯一
25 | title: string; //要显示的标题,一般用中文表示
26 | type: ILeftAsideConfigItemPublicType | ILeftAsideConfigItemPrivateType; //图形的类型
27 | thumbnail: string; //显示到左侧时候的缩略图
28 | svg?: string; //图形的svg代码
29 | props: ILeftAsideConfigItemPublicProps;
30 | }
31 | export interface ILeftAsideConfigItemPrivateSymbol {
32 | symbol_id: string;
33 | symbol_str: string;
34 | width: string;
35 | height: string;
36 | }
37 | export interface ICommonAnimations {
38 | val: string;
39 | delay: string;
40 | speed: string;
41 | repeat: string;
42 | }
43 | export interface ILeftAsideConfigItemPrivate {
44 | symbol?: ILeftAsideConfigItemPrivateSymbol;
45 | common_animations: ICommonAnimations;
46 | }
47 | export type ILeftAsideConfigItem = ILeftAsideConfigItemPublic & ILeftAsideConfigItemPrivate;
48 |
49 | export type GlobalStoreIntention =
50 | | 'none'
51 | | 'create'
52 | | 'beginMulSelect'
53 | | 'adsorbStart'
54 | | 'adsorbEnd'
55 | | 'beginDragCanvas'
56 | | 'runDragCanvas'
57 | | 'endDragCanvas'
58 | | 'showContextMenu'
59 | | 'drawSysLineStart';
60 | export interface IGlobalStoreCreateItemInfo {
61 | config_key: string; //也就是折叠面板的值
62 | item_id: string; //要创建组件的id
63 | }
64 |
65 | export interface IGlobalStoreCanvasCfg {
66 | width: number;
67 | height: number;
68 | scale: number;
69 | color: string;
70 | img: string;
71 | guide: boolean; //参考线
72 | adsorp: boolean; //吸附
73 | adsorp_diff: number;
74 | // 缩放中心
75 | transform_origin: {
76 | x: number;
77 | y: number;
78 | };
79 | // 拖动偏移量
80 | drag_offset: {
81 | x: number;
82 | y: number;
83 | };
84 | }
85 | export interface IGlobalStoreGridCfg {
86 | enabled: boolean;
87 | align: boolean;
88 | size: number;
89 | }
90 | export type DoneJsonEventListType = 'click' | 'dblclick' | 'mouseover' | 'mouseout';
91 | export type DoneJsonEventListAction = 'changeAttr' | 'customCode';
92 | export interface IDoneJsonActionChangeAttr {
93 | id: string;
94 | target_id: string;
95 | target_attr: string | undefined;
96 | target_value: any;
97 | }
98 | export interface IDoneJsonEventList {
99 | id: string;
100 | type: DoneJsonEventListType; // 事件类型
101 | action: DoneJsonEventListAction; // 事件行为
102 | change_attr: IDoneJsonActionChangeAttr[]; //属性更改
103 | custom_code: string;
104 | trigger_rule: {
105 | trigger_id?: string; //触发图形的id
106 | trigger_attr?: string; //触发图形的属性
107 | operator?: string; //运算符
108 | value?: any; //期望值
109 | };
110 | }
111 | export interface IDoneJson {
112 | id: string; //必须唯一
113 | title: string; //标题
114 | type: ILeftAsideConfigItemPublicType | ILeftAsideConfigItemPrivateType; //类型 由配置文件决定
115 | symbol?: ILeftAsideConfigItemPrivateSymbol; //类型是svg的时候需要用这个将svg转换成symbol
116 | binfo: IDoneJsonBinfo;
117 | props: ILeftAsideConfigItemPublicProps;
118 | resize: boolean; //开启缩放
119 | rotate: boolean; //开启旋转
120 | lock: boolean; //锁定
121 | active: boolean; //激活
122 | hide: boolean; //隐藏
123 | common_animations: ICommonAnimations; //通用动画
124 | use_proportional_scaling?: boolean; //使用等比缩放
125 | children?: IDoneJson[];
126 | tag?: string;
127 | thumbnail?: string;
128 | events: IDoneJsonEventList[];
129 | }
130 | //图形边界信息
131 | export interface IDoneJsonBinfo {
132 | left: number;
133 | top: number;
134 | width: number;
135 | height: number;
136 | angle: number;
137 | }
138 | export interface CacheBoundingBox {
139 | id: string;
140 | type: ILeftAsideConfigItemPublicType | ILeftAsideConfigItemPrivateType;
141 | left: number;
142 | top: number;
143 | width: number;
144 | height: number;
145 | bottom: number;
146 | right: number;
147 | }
148 | export type AdsorbPointType = 'tc' | 'bc' | 'lc' | 'rc';
149 | export interface IContextMenuInfo {
150 | title: string;
151 | hot_key: string;
152 | enable: boolean;
153 | }
154 | export type ContextMenuInfoType =
155 | | 'copy'
156 | | 'paste'
157 | | 'delete'
158 | | 'group'
159 | | 'ungroup'
160 | | 'selectAll'
161 | | 'moveTop'
162 | | 'moveUp'
163 | | 'moveDown'
164 | | 'moveBottom';
165 | export interface IContextMenuDetail {
166 | left: number;
167 | top: number;
168 | info: {
169 | [key in ContextMenuInfoType]: IContextMenuInfo;
170 | };
171 | }
172 | export interface IRealTimeData {
173 | show: boolean;
174 | text: string;
175 | }
176 | // 全局状态
177 | export interface IGlobalStore {
178 | intention: GlobalStoreIntention;
179 | create_item_info: IGlobalStoreCreateItemInfo | null;
180 | done_json: IDoneJson[];
181 | selected_items_id: string[];
182 | canvasCfg: IGlobalStoreCanvasCfg;
183 | gridCfg: IGlobalStoreGridCfg;
184 | guideCfg: {
185 | x: {
186 | display: boolean;
187 | top: number;
188 | };
189 | y: {
190 | display: boolean;
191 | left: number;
192 | };
193 | };
194 | lock: boolean;
195 | real_time_data: IRealTimeData;
196 | adsorp_diff: {
197 | x: number;
198 | y: number;
199 | };
200 | setIntention: (val: GlobalStoreIntention) => void;
201 | setCreateItemInfo: (val: IGlobalStoreCreateItemInfo | null) => void;
202 | setGlobalStoreDoneJson: (val: IDoneJson[]) => void;
203 | cancelAllSelect: () => void;
204 | refreshSelectedItemsId: () => void;
205 | deleteSelectedItems: () => void;
206 | setSingleSelect: (id: string) => void;
207 | setSelectItems: (ids: string[]) => void;
208 | setRealTimeData: (val: IRealTimeData) => void;
209 | }
210 | // 左侧配置
211 | export interface ILeftAside {
212 | config: ILeftAsideConfig;
213 | registerConfig: (title: string, config: ILeftAsideConfigItemPublic[]) => void;
214 | }
215 | // 缓存配置
216 | export interface ICache {
217 | boundingBox: CacheBoundingBox[];
218 | setBoundingBox: (val: CacheBoundingBox[]) => void;
219 | adsorbPoint: { type: AdsorbPointType; x: number; y: number; id: string }[];
220 | setAdsorbPoint: (val: { type: AdsorbPointType; x: number; y: number; id: string }[]) => void;
221 | copy: IDoneJson[];
222 | setCopy: (val: IDoneJson[]) => void;
223 | history: IDoneJson[][];
224 | historyIndex: number;
225 | addHistory: (done_json: IDoneJson[]) => void;
226 | }
227 | // 杂项配置
228 | export interface IConfig {
229 | sysComponent: ILeftAsideConfigItem[];
230 | lineRenderOffset: number; //因为连线是使用svg进行渲染的,所以需要一个偏移量和div的画布进行重叠
231 | }
232 | /**
233 | * 右键菜单
234 | */
235 | export interface IContextMenu {
236 | menuInfo: IContextMenuDetail;
237 | setMenuInfo: (val: IContextMenuDetail) => void;
238 | setDisplayItem: (val: ContextMenuInfoType[]) => void;
239 | }
240 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/layout/right-aside/select-item-animate-setting/common-animate.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{
10 | common_animate_list
11 | .map((m) => m.children)
12 | .reduce((pre, curr) => {
13 | return pre.concat(curr);
14 | })
15 | .find((f) => f.value == select_val)?.label
16 | }}
17 |
19 |
新增
22 |
23 |
24 |
30 |
31 |
32 |
40 |
47 | {{ animate.label }}
48 |
49 |
50 |
51 |
53 |
54 |
55 |
56 |
57 |
172 |
193 |
--------------------------------------------------------------------------------
/src/components/mt-edit/components/draw-line-render/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
27 |
28 |
29 |
38 |
39 |
40 |
41 |
76 |
96 |
97 |
98 |
99 |
100 |
204 |
205 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/src/components/mt-edit/composables/sys-line.ts:
--------------------------------------------------------------------------------
1 | import { nextTick } from 'vue';
2 | import type { IDoneJson, IDoneJsonBinfo } from '../store/types';
3 | import { getRectCenterCoordinate, getRectCoordinate, rotatePoint } from '../utils';
4 |
5 | /**
6 | * 更新系统连线实际宽高
7 | * @param sys_lines
8 | * @param scale
9 | */
10 | export const useUpdateSysLineRect = (
11 | sys_lines: IDoneJson[],
12 | canvasDom: HTMLElement,
13 | scale: number
14 | ) => {
15 | sys_lines.forEach((f) => {
16 | const itemRect = document.querySelector(`#${f.id} g .real`)!.getBoundingClientRect();
17 | const canvas_area_bounding_info = canvasDom!.getBoundingClientRect();
18 | const new_left = (itemRect?.left - canvas_area_bounding_info?.left) / scale;
19 | const new_top = (itemRect?.top - canvas_area_bounding_info?.top) / scale;
20 | const move_x = new_left - f.binfo.left;
21 | const move_y = new_top - f.binfo.top;
22 | f.binfo.left = new_left;
23 | f.binfo.top = new_top;
24 | f.binfo.width = itemRect?.width / scale;
25 | f.binfo.height = itemRect?.height / scale;
26 | f.props.point_position = {
27 | ...f.props.point_position,
28 | val: f.props.point_position.val.map((m: { x: number; y: number }) => {
29 | return {
30 | x: m.x - move_x,
31 | y: m.y - move_y
32 | };
33 | })
34 | };
35 | });
36 | };
37 | /**
38 | * 更新系统连线
39 | * @param sys_lines 要更新的连线列表
40 | * @param done_json 所有组件信息
41 | * @param canvasDom 画布dom
42 | * @param scale 画布缩放
43 | */
44 | export const useUpdateSysLine = (
45 | sys_lines: IDoneJson[],
46 | done_json: IDoneJson[],
47 | canvasDom: HTMLElement,
48 | scale: number,
49 | move_binfo?: IDoneJsonBinfo & { id: string }
50 | ) => {
51 | const temp_done_json = [...done_json];
52 | sys_lines.forEach((f) => {
53 | if (!f.props.bind_anchors.val.start && !f.props.bind_anchors.val.end) {
54 | return;
55 | }
56 | const itemRect = document.querySelector(`#${f.id} g .real`)!.getBoundingClientRect();
57 | const canvas_area_bounding_info = canvasDom!.getBoundingClientRect();
58 | const new_left = (itemRect?.left - canvas_area_bounding_info?.left) / scale;
59 | const new_top = (itemRect?.top - canvas_area_bounding_info?.top) / scale;
60 |
61 | // 处理起点绑定
62 | if (f.props.bind_anchors.val.start) {
63 | // 根据id和类型找到锚点坐标
64 | const find_item = temp_done_json.find((m) => m.id === f.props.bind_anchors.val.start.id);
65 | if (find_item) {
66 | const b_info = find_item.id === move_binfo?.id ? move_binfo : find_item.binfo;
67 | // 四个角原始坐标
68 | const { topLeft, topRight, bottomLeft, bottomRight } = getRectCoordinate(b_info);
69 | // 四条边中点坐标
70 | const { topCenter, bottomCenter, leftCenter, rightCenter } = getRectCenterCoordinate(
71 | topLeft,
72 | topRight,
73 | bottomLeft,
74 | bottomRight
75 | );
76 | // 旋转中心
77 | const centerX = topCenter.x;
78 | const centerY = leftCenter.y;
79 |
80 | // 旋转角度(弧度)
81 | const angleRad = (Math.PI / 180) * find_item.binfo.angle;
82 |
83 | if (f.props.bind_anchors.val.start.type === 'tc') {
84 | const new_tc = rotatePoint(topCenter.x, topCenter.y, centerX, centerY, angleRad);
85 | f.props.point_position.val[0] = {
86 | x: new_tc.x - f.binfo.left,
87 | y: new_tc.y - f.binfo.top
88 | };
89 | } else if (f.props.bind_anchors.val.start.type === 'bc') {
90 | const new_bc = rotatePoint(bottomCenter.x, bottomCenter.y, centerX, centerY, angleRad);
91 | f.props.point_position.val[0] = {
92 | x: new_bc.x - f.binfo.left,
93 | y: new_bc.y - f.binfo.top
94 | };
95 | } else if (f.props.bind_anchors.val.start.type === 'lc') {
96 | const new_lc = rotatePoint(leftCenter.x, leftCenter.y, centerX, centerY, angleRad);
97 | f.props.point_position.val[0] = {
98 | x: new_lc.x - f.binfo.left,
99 | y: new_lc.y - f.binfo.top
100 | };
101 | } else if (f.props.bind_anchors.val.start.type === 'rc') {
102 | const new_rc = rotatePoint(rightCenter.x, rightCenter.y, centerX, centerY, angleRad);
103 | f.props.point_position.val[0] = {
104 | x: new_rc.x - f.binfo.left,
105 | y: new_rc.y - f.binfo.top
106 | };
107 | }
108 | const move_x = new_left - f.binfo.left;
109 | const move_y = new_top - f.binfo.top;
110 | f.binfo = {
111 | ...f.binfo,
112 | left: new_left,
113 | top: new_top,
114 | width: itemRect?.width / scale,
115 | height: itemRect?.height / scale
116 | };
117 | f.props.point_position = {
118 | ...f.props.point_position,
119 | val: f.props.point_position.val.map((m: { x: number; y: number }) => {
120 | return {
121 | x: m.x - move_x,
122 | y: m.y - move_y
123 | };
124 | })
125 | };
126 | } else {
127 | f.props.bind_anchors.val.start = null;
128 | }
129 | }
130 | // 处理终点绑定
131 | if (f.props.bind_anchors.val.end) {
132 | // 根据id和类型找到锚点坐标
133 | const find_item = temp_done_json.find((m) => m.id === f.props.bind_anchors.val.end.id);
134 | if (find_item) {
135 | const b_info = find_item.id === move_binfo?.id ? move_binfo : find_item.binfo;
136 | // 四个角原始坐标
137 | const { topLeft, topRight, bottomLeft, bottomRight } = getRectCoordinate(b_info);
138 | // 四条边中点坐标
139 | const { topCenter, bottomCenter, leftCenter, rightCenter } = getRectCenterCoordinate(
140 | topLeft,
141 | topRight,
142 | bottomLeft,
143 | bottomRight
144 | );
145 | // 旋转中心
146 | const centerX = topCenter.x;
147 | const centerY = leftCenter.y;
148 |
149 | // 旋转角度(弧度)
150 | const angleRad = (Math.PI / 180) * find_item.binfo.angle;
151 |
152 | if (f.props.bind_anchors.val.end.type === 'tc') {
153 | const new_tc = rotatePoint(topCenter.x, topCenter.y, centerX, centerY, angleRad);
154 | f.props.point_position.val[f.props.point_position.val.length - 1] = {
155 | x: new_tc.x - f.binfo.left,
156 | y: new_tc.y - f.binfo.top
157 | };
158 | } else if (f.props.bind_anchors.val.end.type === 'bc') {
159 | const new_bc = rotatePoint(bottomCenter.x, bottomCenter.y, centerX, centerY, angleRad);
160 | f.props.point_position.val[f.props.point_position.val.length - 1] = {
161 | x: new_bc.x - f.binfo.left,
162 | y: new_bc.y - f.binfo.top
163 | };
164 | } else if (f.props.bind_anchors.val.end.type === 'lc') {
165 | const new_lc = rotatePoint(leftCenter.x, leftCenter.y, centerX, centerY, angleRad);
166 | f.props.point_position.val[f.props.point_position.val.length - 1] = {
167 | x: new_lc.x - f.binfo.left,
168 | y: new_lc.y - f.binfo.top
169 | };
170 | } else if (f.props.bind_anchors.val.end.type === 'rc') {
171 | const new_rc = rotatePoint(rightCenter.x, rightCenter.y, centerX, centerY, angleRad);
172 | f.props.point_position.val[f.props.point_position.val.length - 1] = {
173 | x: new_rc.x - f.binfo.left,
174 | y: new_rc.y - f.binfo.top
175 | };
176 | }
177 | const move_x = new_left - f.binfo.left;
178 | const move_y = new_top - f.binfo.top;
179 | f.binfo = {
180 | ...f.binfo,
181 | left: new_left,
182 | top: new_top,
183 | width: itemRect?.width / scale,
184 | height: itemRect?.height / scale
185 | };
186 | f.props.point_position = {
187 | ...f.props.point_position,
188 | val: f.props.point_position.val.map((m: { x: number; y: number }) => {
189 | return {
190 | x: m.x - move_x,
191 | y: m.y - move_y
192 | };
193 | })
194 | };
195 | } else {
196 | f.props.bind_anchors.val.end = null;
197 | }
198 | }
199 | });
200 | // 直接写在这里会损失一部分性能 也可以注释掉下面的 然后根据需求在useUpdateSysLine之后手动调用useUpdateSysLineRect
201 | nextTick(() => {
202 | useUpdateSysLineRect(sys_lines, canvasDom, scale);
203 | });
204 | };
205 |
--------------------------------------------------------------------------------