feat: 创建设备布点模块

dev-deviceSetting
donghao 12 months ago
parent 9ccb5be3fc
commit 35cfec51ec

@ -0,0 +1,67 @@
{
"globals": {
"Component": true,
"ComponentPublicInstance": true,
"ComputedRef": true,
"EffectScope": true,
"ExtractDefaultPropTypes": true,
"ExtractPropTypes": true,
"ExtractPublicPropTypes": true,
"InjectionKey": true,
"PropType": true,
"Ref": true,
"VNode": true,
"WritableComputedRef": true,
"computed": true,
"createApp": true,
"customRef": true,
"defineAsyncComponent": true,
"defineComponent": true,
"effectScope": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"h": true,
"inject": true,
"isProxy": true,
"isReactive": true,
"isReadonly": true,
"isRef": true,
"markRaw": true,
"nextTick": true,
"onActivated": true,
"onBeforeMount": true,
"onBeforeUnmount": true,
"onBeforeUpdate": true,
"onDeactivated": true,
"onErrorCaptured": true,
"onMounted": true,
"onRenderTracked": true,
"onRenderTriggered": true,
"onScopeDispose": true,
"onServerPrefetch": true,
"onUnmounted": true,
"onUpdated": true,
"provide": true,
"reactive": true,
"readonly": true,
"ref": true,
"resolveComponent": true,
"shallowReactive": true,
"shallowReadonly": true,
"shallowRef": true,
"toRaw": true,
"toRef": true,
"toRefs": true,
"toValue": true,
"triggerRef": true,
"unref": true,
"useAttrs": true,
"useCssModule": true,
"useCssVars": true,
"useSlots": true,
"watch": true,
"watchEffect": true,
"watchPostEffect": true,
"watchSyncEffect": true
}
}

78
auto-imports.d.ts vendored

@ -0,0 +1,78 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
const EffectScope: typeof import("vue")["EffectScope"];
const computed: typeof import("vue")["computed"];
const createApp: typeof import("vue")["createApp"];
const customRef: typeof import("vue")["customRef"];
const defineAsyncComponent: typeof import("vue")["defineAsyncComponent"];
const defineComponent: typeof import("vue")["defineComponent"];
const effectScope: typeof import("vue")["effectScope"];
const getCurrentInstance: typeof import("vue")["getCurrentInstance"];
const getCurrentScope: typeof import("vue")["getCurrentScope"];
const h: typeof import("vue")["h"];
const inject: typeof import("vue")["inject"];
const isProxy: typeof import("vue")["isProxy"];
const isReactive: typeof import("vue")["isReactive"];
const isReadonly: typeof import("vue")["isReadonly"];
const isRef: typeof import("vue")["isRef"];
const markRaw: typeof import("vue")["markRaw"];
const nextTick: typeof import("vue")["nextTick"];
const onActivated: typeof import("vue")["onActivated"];
const onBeforeMount: typeof import("vue")["onBeforeMount"];
const onBeforeUnmount: typeof import("vue")["onBeforeUnmount"];
const onBeforeUpdate: typeof import("vue")["onBeforeUpdate"];
const onDeactivated: typeof import("vue")["onDeactivated"];
const onErrorCaptured: typeof import("vue")["onErrorCaptured"];
const onMounted: typeof import("vue")["onMounted"];
const onRenderTracked: typeof import("vue")["onRenderTracked"];
const onRenderTriggered: typeof import("vue")["onRenderTriggered"];
const onScopeDispose: typeof import("vue")["onScopeDispose"];
const onServerPrefetch: typeof import("vue")["onServerPrefetch"];
const onUnmounted: typeof import("vue")["onUnmounted"];
const onUpdated: typeof import("vue")["onUpdated"];
const provide: typeof import("vue")["provide"];
const reactive: typeof import("vue")["reactive"];
const readonly: typeof import("vue")["readonly"];
const ref: typeof import("vue")["ref"];
const resolveComponent: typeof import("vue")["resolveComponent"];
const shallowReactive: typeof import("vue")["shallowReactive"];
const shallowReadonly: typeof import("vue")["shallowReadonly"];
const shallowRef: typeof import("vue")["shallowRef"];
const toRaw: typeof import("vue")["toRaw"];
const toRef: typeof import("vue")["toRef"];
const toRefs: typeof import("vue")["toRefs"];
const toValue: typeof import("vue")["toValue"];
const triggerRef: typeof import("vue")["triggerRef"];
const unref: typeof import("vue")["unref"];
const useAttrs: typeof import("vue")["useAttrs"];
const useCssModule: typeof import("vue")["useCssModule"];
const useCssVars: typeof import("vue")["useCssVars"];
const useSlots: typeof import("vue")["useSlots"];
const watch: typeof import("vue")["watch"];
const watchEffect: typeof import("vue")["watchEffect"];
const watchPostEffect: typeof import("vue")["watchPostEffect"];
const watchSyncEffect: typeof import("vue")["watchSyncEffect"];
}
// for type re-export
declare global {
// @ts-ignore
export type {
Component,
ComponentPublicInstance,
ComputedRef,
ExtractDefaultPropTypes,
ExtractPropTypes,
ExtractPublicPropTypes,
InjectionKey,
PropType,
Ref,
VNode,
WritableComputedRef
} from "vue";
import("vue");
}

@ -46,3 +46,6 @@ login:
usernameReg: Please enter username
passwordReg: Please enter password
passwordRuleReg: The password format should be any combination of 8-18 digits
flip:
x: flip x
y: flip y

@ -52,3 +52,6 @@ login:
usernameReg: 请输入账号
passwordReg: 请输入密码
passwordRuleReg: 密码格式应为8-18位数字、字母、符号的任意两种组合
flip:
x: 水平翻转
y: 垂直翻转

@ -40,9 +40,12 @@
"echarts": "^5.4.2",
"echarts-gl": "^2.0.9",
"element-plus": "2.3.6",
"events": "^3.3.0",
"fabric": "^5.3.0",
"hotkeys-js": "^3.13.7",
"js-cookie": "^3.0.5",
"lib-flexible": "^0.3.2",
"lodash-es": "^4.17.21",
"lottie-web": "^5.12.2",
"mitt": "^3.0.0",
"mockjs": "^1.1.0",
@ -54,12 +57,15 @@
"responsive-storage": "^2.2.0",
"sortablejs": "^1.15.0",
"swiper": "^11.0.5",
"tapable": "^2.2.1",
"uuid": "^10.0.0",
"v3-infinite-loading": "^1.2.2",
"vue": "^3.3.4",
"vue-i18n": "^9.2.2",
"vue-router": "^4.2.2",
"vue-types": "^5.1.0",
"vue-waterfall-plugin-next": "^2.2.1",
"vue3-lazyload": "^0.3.8",
"vue3-scale-box": "^0.1.9"
},
"devDependencies": {
@ -105,6 +111,8 @@
"tailwindcss": "^3.3.2",
"terser": "^5.18.1",
"typescript": "5.0.4",
"unplugin-auto-import": "^0.18.2",
"unplugin-vue-components": "^0.27.3",
"vite": "^4.3.9",
"vite-plugin-cdn-import": "^0.3.5",
"vite-plugin-compression": "^0.5.1",

@ -41,15 +41,24 @@ dependencies:
element-plus:
specifier: 2.3.6
version: 2.3.6(vue@3.3.4)
events:
specifier: ^3.3.0
version: 3.3.0
fabric:
specifier: ^5.3.0
version: 5.3.0
hotkeys-js:
specifier: ^3.13.7
version: 3.13.7
js-cookie:
specifier: ^3.0.5
version: 3.0.5
lib-flexible:
specifier: ^0.3.2
version: 0.3.2
lodash-es:
specifier: ^4.17.21
version: 4.17.21
lottie-web:
specifier: ^5.12.2
version: 5.12.2
@ -83,6 +92,12 @@ dependencies:
swiper:
specifier: ^11.0.5
version: 11.0.5
tapable:
specifier: ^2.2.1
version: 2.2.1
uuid:
specifier: ^10.0.0
version: 10.0.0
v3-infinite-loading:
specifier: ^1.2.2
version: 1.2.2
@ -101,12 +116,12 @@ dependencies:
vue-waterfall-plugin-next:
specifier: ^2.2.1
version: 2.2.1(vue@3.3.4)
vue3-lazyload:
specifier: ^0.3.8
version: 0.3.8(vue@3.3.4)
vue3-scale-box:
specifier: ^0.1.9
version: 0.1.9
安装插件:
specifier: link://安装插件
version: link:../../../../安装插件
devDependencies:
"@commitlint/cli":
@ -235,6 +250,12 @@ devDependencies:
typescript:
specifier: 5.0.4
version: 5.0.4
unplugin-auto-import:
specifier: ^0.18.2
version: 0.18.2(@vueuse/core@10.2.0)
unplugin-vue-components:
specifier: ^0.27.3
version: 0.27.3(vue@3.3.4)
vite:
specifier: ^4.3.9
version: 4.3.9(@types/node@20.3.1)(sass@1.63.6)(terser@5.18.1)
@ -279,6 +300,13 @@ packages:
"@jridgewell/gen-mapping": 0.3.3
"@jridgewell/trace-mapping": 0.3.18
/@antfu/utils@0.7.10:
resolution:
{
integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==
}
dev: true
/@babel/code-frame@7.22.5:
resolution:
{
@ -1559,6 +1587,13 @@ packages:
integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
}
/@jridgewell/sourcemap-codec@1.5.0:
resolution:
{
integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
}
dev: true
/@jridgewell/trace-mapping@0.3.18:
resolution:
{
@ -1824,6 +1859,23 @@ packages:
estree-walker: 2.0.2
picomatch: 2.3.1
/@rollup/pluginutils@5.1.0:
resolution:
{
integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==
}
engines: { node: ">=14.0.0" }
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
dependencies:
"@types/estree": 1.0.1
estree-walker: 2.0.2
picomatch: 2.3.1
dev: true
/@sxzz/popperjs-es@2.11.7:
resolution:
{
@ -2003,7 +2055,6 @@ packages:
{
integrity: sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA==
}
dev: false
/@types/web-bluetooth@0.0.20:
resolution:
@ -2450,7 +2501,6 @@ packages:
transitivePeerDependencies:
- "@vue/composition-api"
- vue
dev: false
/@vueuse/core@10.8.0(vue@3.3.4):
resolution:
@ -2476,7 +2526,7 @@ packages:
"@types/web-bluetooth": 0.0.16
"@vueuse/metadata": 9.13.0
"@vueuse/shared": 9.13.0(vue@3.3.4)
vue-demi: 0.14.5(vue@3.3.4)
vue-demi: 0.14.7(vue@3.3.4)
transitivePeerDependencies:
- "@vue/composition-api"
- vue
@ -2487,7 +2537,6 @@ packages:
{
integrity: sha512-IR7Mkq6QSgZ38q/2ZzOt+Zz1OpcEsnwE64WBumDQ+RGKrosFCtUA2zgRrOqDEzPBXrVB+4HhFkwDjQMu0fDBKw==
}
dev: false
/@vueuse/metadata@10.8.0:
resolution:
@ -2532,11 +2581,10 @@ packages:
integrity: sha512-dIeA8+g9Av3H5iF4NXR/sft4V6vys76CpZ6hxwj8eMXybXk2WRl3scSsOVi+kQ9SX38COR7AH7WwY83UcuxbSg==
}
dependencies:
vue-demi: 0.14.5(vue@3.3.4)
vue-demi: 0.14.7(vue@3.3.4)
transitivePeerDependencies:
- "@vue/composition-api"
- vue
dev: false
/@vueuse/shared@10.8.0(vue@3.3.4):
resolution:
@ -2556,7 +2604,7 @@ packages:
integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==
}
dependencies:
vue-demi: 0.14.5(vue@3.3.4)
vue-demi: 0.14.7(vue@3.3.4)
transitivePeerDependencies:
- "@vue/composition-api"
- vue
@ -2666,6 +2714,14 @@ packages:
engines: { node: ">=0.4.0" }
hasBin: true
/acorn@8.12.1:
resolution:
{
integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
}
engines: { node: ">=0.4.0" }
hasBin: true
/acorn@8.9.0:
resolution:
{
@ -3194,6 +3250,24 @@ packages:
optionalDependencies:
fsevents: 2.3.2
/chokidar@3.6.0:
resolution:
{
integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
}
engines: { node: ">= 8.10.0" }
dependencies:
anymatch: 3.1.3
braces: 3.0.2
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.3
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.2
dev: true
/chownr@2.0.0:
resolution:
{
@ -3409,6 +3483,12 @@ packages:
integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
}
/confbox@0.1.7:
resolution:
{
integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==
}
/connect@3.7.0:
resolution:
{
@ -3900,6 +3980,21 @@ packages:
dependencies:
ms: 2.1.2
/debug@4.3.6:
resolution:
{
integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==
}
engines: { node: ">=6.0" }
peerDependencies:
supports-color: "*"
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.2
dev: true
/decamelize-keys@1.1.1:
resolution:
{
@ -4375,8 +4470,6 @@ packages:
}
engines: { node: ">=12" }
requiresBuild: true
dev: false
optional: true
/escodegen@2.0.0:
resolution:
@ -4633,8 +4726,6 @@ packages:
requiresBuild: true
dependencies:
"@types/estree": 1.0.1
dev: false
optional: true
/esutils@2.0.3:
resolution:
@ -4643,6 +4734,14 @@ packages:
}
engines: { node: ">=0.10.0" }
/events@3.3.0:
resolution:
{
integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
}
engines: { node: ">=0.8.x" }
dev: false
/execa@4.1.0:
resolution:
{
@ -4740,6 +4839,20 @@ packages:
merge2: 1.4.1
micromatch: 4.0.5
/fast-glob@3.3.2:
resolution:
{
integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
}
engines: { node: ">=8.6.0" }
dependencies:
"@nodelib/fs.stat": 2.0.5
"@nodelib/fs.walk": 1.2.8
glob-parent: 5.1.2
merge2: 1.4.1
micromatch: 4.0.5
dev: true
/fast-json-stable-stringify@2.1.0:
resolution:
{
@ -5302,6 +5415,13 @@ packages:
lru-cache: 6.0.0
dev: true
/hotkeys-js@3.13.7:
resolution:
{
integrity: sha512-ygFIdTqqwG4fFP7kkiYlvayZppeIQX2aPpirsngkv1xM1lP0piDY5QEh68nQnIKvz64hfocxhBaD/uK3sSK1yQ==
}
dev: false
/html-encoding-sniffer@3.0.0:
resolution:
{
@ -5714,6 +5834,13 @@ packages:
}
dev: true
/js-tokens@9.0.0:
resolution:
{
integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==
}
dev: true
/js-yaml@4.1.0:
resolution:
{
@ -5974,6 +6101,17 @@ packages:
dev: false
optional: true
/local-pkg@0.5.0:
resolution:
{
integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==
}
engines: { node: ">=14" }
dependencies:
mlly: 1.7.1
pkg-types: 1.0.3
dev: true
/locate-path@5.0.0:
resolution:
{
@ -6162,6 +6300,15 @@ packages:
dependencies:
"@jridgewell/sourcemap-codec": 1.4.15
/magic-string@0.30.11:
resolution:
{
integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==
}
dependencies:
"@jridgewell/sourcemap-codec": 1.5.0
dev: true
/make-dir@3.1.0:
resolution:
{
@ -6350,6 +6497,16 @@ packages:
brace-expansion: 2.0.1
dev: true
/minimatch@9.0.5:
resolution:
{
integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
}
engines: { node: ">=16 || 14 >=14.17" }
dependencies:
brace-expansion: 2.0.1
dev: true
/minimist-options@4.1.0:
resolution:
{
@ -6441,6 +6598,17 @@ packages:
pkg-types: 1.0.3
ufo: 1.1.2
/mlly@1.7.1:
resolution:
{
integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==
}
dependencies:
acorn: 8.12.1
pathe: 1.1.2
pkg-types: 1.1.3
ufo: 1.5.4
/mockjs@1.1.0:
resolution:
{
@ -6979,6 +7147,12 @@ packages:
integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==
}
/pathe@1.1.2:
resolution:
{
integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==
}
/perfect-debounce@1.0.0:
resolution:
{
@ -7062,9 +7236,19 @@ packages:
requiresBuild: true
dependencies:
jsonc-parser: 3.2.0
mlly: 1.4.0
mlly: 1.7.1
pathe: 1.1.1
/pkg-types@1.1.3:
resolution:
{
integrity: sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==
}
dependencies:
confbox: 0.1.7
mlly: 1.7.1
pathe: 1.1.2
/popmotion@11.0.5:
resolution:
{
@ -8457,6 +8641,13 @@ packages:
dev: false
optional: true
/scule@1.3.0:
resolution:
{
integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==
}
dev: true
/semver@5.7.1:
resolution:
{
@ -8862,6 +9053,15 @@ packages:
dev: false
optional: true
/strip-literal@2.1.0:
resolution:
{
integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==
}
dependencies:
js-tokens: 9.0.0
dev: true
/style-value-types@5.1.2:
resolution:
{
@ -9045,6 +9245,14 @@ packages:
dev: false
optional: true
/tapable@2.2.1:
resolution:
{
integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
}
engines: { node: ">=6" }
dev: false
/tar@6.1.15:
resolution:
{
@ -9339,6 +9547,12 @@ packages:
}
requiresBuild: true
/ufo@1.5.4:
resolution:
{
integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==
}
/unctx@2.3.1:
resolution:
{
@ -9376,6 +9590,29 @@ packages:
dev: false
optional: true
/unimport@3.10.0:
resolution:
{
integrity: sha512-/UvKRfWx3mNDWwWQhR62HsoM3wxHwYdTq8ellZzMOHnnw4Dp8tovgthyW7DjTrbjDL+i4idOp06voz2VKlvrLw==
}
dependencies:
"@rollup/pluginutils": 5.1.0
acorn: 8.12.1
escape-string-regexp: 5.0.0
estree-walker: 3.0.3
fast-glob: 3.3.2
local-pkg: 0.5.0
magic-string: 0.30.11
mlly: 1.7.1
pathe: 1.1.2
pkg-types: 1.1.3
scule: 1.3.0
strip-literal: 2.1.0
unplugin: 1.12.0
transitivePeerDependencies:
- rollup
dev: true
/universalify@0.2.0:
resolution:
{
@ -9401,6 +9638,79 @@ packages:
engines: { node: ">= 0.8" }
dev: true
/unplugin-auto-import@0.18.2(@vueuse/core@10.2.0):
resolution:
{
integrity: sha512-Dwb3rAic75harVBrVjwiq6H24PT+nBq2dpxV5BH8NNI6sDFaTytvP+iyo4xy7prQbR3r5K6nMs4f5Wp9PE4g8A==
}
engines: { node: ">=14" }
peerDependencies:
"@nuxt/kit": ^3.2.2
"@vueuse/core": "*"
peerDependenciesMeta:
"@nuxt/kit":
optional: true
"@vueuse/core":
optional: true
dependencies:
"@antfu/utils": 0.7.10
"@rollup/pluginutils": 5.1.0
"@vueuse/core": 10.2.0(vue@3.3.4)
fast-glob: 3.3.2
local-pkg: 0.5.0
magic-string: 0.30.11
minimatch: 9.0.5
unimport: 3.10.0
unplugin: 1.12.0
transitivePeerDependencies:
- rollup
dev: true
/unplugin-vue-components@0.27.3(vue@3.3.4):
resolution:
{
integrity: sha512-5wg7lbdg5ZcrAQNzyYK+6gcg/DG8K6rO+f5YeuvqGHs/PhpapBvpA4O/0ex/pFthE5WgRk43iWuRZEMLVsdz4Q==
}
engines: { node: ">=14" }
peerDependencies:
"@babel/parser": ^7.15.8
"@nuxt/kit": ^3.2.2
vue: 2 || 3
peerDependenciesMeta:
"@babel/parser":
optional: true
"@nuxt/kit":
optional: true
dependencies:
"@antfu/utils": 0.7.10
"@rollup/pluginutils": 5.1.0
chokidar: 3.6.0
debug: 4.3.6
fast-glob: 3.3.2
local-pkg: 0.5.0
magic-string: 0.30.11
minimatch: 9.0.5
mlly: 1.7.1
unplugin: 1.12.0
vue: 3.3.4
transitivePeerDependencies:
- rollup
- supports-color
dev: true
/unplugin@1.12.0:
resolution:
{
integrity: sha512-KeczzHl2sATPQUx1gzo+EnUkmN4VmGBYRRVOZSGvGITE9rGHRDGqft6ONceP3vgXcyJ2XjX5axG5jMWUwNCYLw==
}
engines: { node: ">=14.0.0" }
dependencies:
acorn: 8.12.1
chokidar: 3.6.0
webpack-sources: 3.2.3
webpack-virtual-modules: 0.6.2
dev: true
/unplugin@1.3.1:
resolution:
{
@ -9488,6 +9798,14 @@ packages:
engines: { node: ">= 0.4.0" }
dev: true
/uuid@10.0.0:
resolution:
{
integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==
}
hasBin: true
dev: false
/uuid@8.3.2:
resolution:
{
@ -9628,6 +9946,24 @@ packages:
fsevents: 2.3.2
dev: true
/vue-demi@0.12.5(vue@3.3.4):
resolution:
{
integrity: sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==
}
engines: { node: ">=12" }
hasBin: true
requiresBuild: true
peerDependencies:
"@vue/composition-api": ^1.0.0-rc.1
vue: ^3.0.0-0 || ^2.6.0
peerDependenciesMeta:
"@vue/composition-api":
optional: true
dependencies:
vue: 3.3.4
dev: false
/vue-demi@0.14.5(vue@3.3.4):
resolution:
{
@ -9644,7 +9980,6 @@ packages:
optional: true
dependencies:
vue: 3.3.4
dev: false
/vue-demi@0.14.7(vue@3.3.4):
resolution:
@ -9662,7 +9997,6 @@ packages:
optional: true
dependencies:
vue: 3.3.4
dev: false
/vue-eslint-parser@9.3.1(eslint@8.43.0):
resolution:
@ -9780,6 +10114,22 @@ packages:
- vue
dev: false
/vue3-lazyload@0.3.8(vue@3.3.4):
resolution:
{
integrity: sha512-UiJHRT7mzry102WbhtrRgJh+f8Z8u4Z+H1RU4dvPmQeq7wFSDFxZB9iJOWGihH2FscXN/8rMGLDOQJAmjwqpCg==
}
peerDependencies:
"@vue/composition-api": ^1.0.0-rc.1
vue: ^2.0.0 || >=3.0.0
peerDependenciesMeta:
"@vue/composition-api":
optional: true
dependencies:
vue: 3.3.4
vue-demi: 0.12.5(vue@3.3.4)
dev: false
/vue3-scale-box@0.1.9:
resolution:
{
@ -9851,6 +10201,13 @@ packages:
integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==
}
/webpack-virtual-modules@0.6.2:
resolution:
{
integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==
}
dev: true
/whatwg-encoding@2.0.0:
resolution:
{

@ -0,0 +1,17 @@
<svg width="19" height="18" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d)">
<circle cx="9.99609" cy="9" r="5" fill="white"/>
<circle cx="9.99609" cy="9" r="4.75" stroke="black" stroke-opacity="0.3" stroke-width="0.5"/>
</g>
<defs>
<filter id="filter0_d" x="0.996094" y="0" width="18" height="18" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.137674 0 0 0 0 0.190937 0 0 0 0 0.270833 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 904 B

@ -0,0 +1,17 @@
<svg width="12" height="24" viewBox="0 0 12 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d)">
<rect x="4" y="4" width="4" height="16" rx="2" fill="white"/>
<rect x="4.25" y="4.25" width="3.5" height="15.5" rx="1.75" stroke="black" stroke-opacity="0.3" stroke-width="0.5"/>
</g>
<defs>
<filter id="filter0_d" x="0" y="0" width="12" height="24" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.137674 0 0 0 0 0.190937 0 0 0 0 0.270833 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 933 B

@ -0,0 +1,17 @@
<svg width="24" height="12" viewBox="0 0 24 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d)">
<rect x="20" y="4" width="4" height="16" rx="2" transform="rotate(90 20 4)" fill="white"/>
<rect x="19.75" y="4.25" width="3.5" height="15.5" rx="1.75" transform="rotate(90 19.75 4.25)" stroke="black" stroke-opacity="0.3" stroke-width="0.5"/>
</g>
<defs>
<filter id="filter0_d" x="0" y="0" width="24" height="12" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.137674 0 0 0 0 0.190937 0 0 0 0 0.270833 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 997 B

@ -0,0 +1,20 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d)">
<circle cx="9" cy="9" r="5" fill="white"/>
<circle cx="9" cy="9" r="4.75" stroke="black" stroke-opacity="0.3" stroke-width="0.5"/>
</g>
<path d="M10.8047 11.1242L9.49934 11.1242L9.49934 9.81885" stroke="black" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.94856 6.72607L8.25391 6.72607L8.25391 8.03142" stroke="black" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.69517 6.92267C10.007 7.03301 10.2858 7.22054 10.5055 7.46776C10.7252 7.71497 10.8787 8.01382 10.9517 8.33642C11.0247 8.65902 11.0148 8.99485 10.9229 9.31258C10.831 9.63031 10.6601 9.91958 10.4262 10.1534L9.49701 11.0421M8.25792 6.72607L7.30937 7.73554C7.07543 7.96936 6.90454 8.25863 6.81264 8.57636C6.72073 8.89408 6.71081 9.22992 6.78381 9.55251C6.8568 9.87511 7.01032 10.174 7.23005 10.4212C7.44978 10.6684 7.72855 10.8559 8.04036 10.9663" stroke="black" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round"/>
<defs>
<filter id="filter0_d" x="0" y="0" width="18" height="18" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset/>
<feGaussianBlur stdDeviation="2"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.137674 0 0 0 0 0.190937 0 0 0 0 0.270833 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

@ -0,0 +1,9 @@
@font-face {
font-family: "汉体";
src: url("./cn/汉体.ttf");
}
@font-face {
font-family: "华康金刚黑";
src: url("./cn/华康金刚黑.ttf");
}

@ -0,0 +1,22 @@
/*
* @Author: 秦少卫
* @Date: 2022-09-05 22:54:14
* @LastEditors: 秦少卫
* @LastEditTime: 2022-09-05 22:59:30
* @Description: 字体文件列表
*/
const cnList = [
{
name: "汉体",
fontFamily: "汉体"
},
{
name: "华康金刚黑",
fontFamily: "华康金刚黑"
}
];
const enList = [];
export default [...cnList, ...enList];

@ -0,0 +1,6 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 17.3334V28" stroke="#333333" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 22H8.66667L11.3333 18" stroke="#333333" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.66699 13.3334L22.316 25.0994C22.5303 25.2422 22.8079 25.2488 23.0287 25.1163L29.3337 21.3334" stroke="#333333" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.71876 8.47912C4.35533 8.23685 4.31919 7.71632 4.64565 7.42612L9.61867 3.00568C9.84374 2.8056 10.175 2.78127 10.4269 2.94631L28.4698 14.7675C28.8747 15.0329 28.8708 15.6277 28.4624 15.8876L22.3665 19.7669C22.1437 19.9086 21.8584 19.9055 21.6387 19.7591L4.71876 8.47912Z" stroke="#333333" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 843 B

@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="39" viewBox="0 0 36 39" fill="none">
<g filter="url(#filter0_d_4310_40111)">
<mask id="path-1-inside-1_4310_40111" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.2704 25.9335C25.3006 25.299 30 20.1983 30 14C30 7.37258 24.6274 2 18 2C11.3726 2 6 7.37258 6 14C6 19.8172 10.1393 24.6677 15.6332 25.7667L17.5 29L19.2704 25.9335Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.2704 25.9335C25.3006 25.299 30 20.1983 30 14C30 7.37258 24.6274 2 18 2C11.3726 2 6 7.37258 6 14C6 19.8172 10.1393 24.6677 15.6332 25.7667L17.5 29L19.2704 25.9335Z" fill="white"/>
<path d="M19.2704 25.9335L19.1658 24.939L18.6591 24.9924L18.4044 25.4335L19.2704 25.9335ZM15.6332 25.7667L16.4993 25.2667L16.273 24.8748L15.8294 24.7861L15.6332 25.7667ZM17.5 29L16.634 29.5L17.5 31L18.366 29.5L17.5 29ZM29 14C29 19.6811 24.6924 24.3575 19.1658 24.939L19.3751 26.9281C25.9088 26.2405 31 20.7155 31 14H29ZM18 3C24.0751 3 29 7.92487 29 14H31C31 6.8203 25.1797 1 18 1V3ZM7 14C7 7.92487 11.9249 3 18 3V1C10.8203 1 5 6.8203 5 14H7ZM15.8294 24.7861C10.7937 23.7788 7 19.3313 7 14H5C5 20.3032 9.48488 25.5566 15.4371 26.7472L15.8294 24.7861ZM14.7672 26.2667L16.634 29.5L18.366 28.5L16.4993 25.2667L14.7672 26.2667ZM18.366 29.5L20.1364 26.4335L18.4044 25.4335L16.634 28.5L18.366 29.5Z" fill="#DCDCDC" mask="url(#path-1-inside-1_4310_40111)"/>
</g>
<defs>
<filter id="filter0_d_4310_40111" x="0" y="0" width="36" height="39" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="3"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4310_40111"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_4310_40111" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M14.3334 2H7.66675H1.66675" stroke="#52C41A" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.66675 7.66667V2" stroke="#52C41A" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.80841 6.52734L13.4336 9.37438L12.8528 10.254L11.5187 12.6573L10.9379 13.5369L1.60059 11.035L2.80841 6.52734Z" fill="#52C41A" stroke="#52C41A" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.8527 10.2539L14.1406 10.599L13.4504 13.1748L11.5186 12.6572" stroke="#52C41A" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="4.5" cy="9.5" r="1.25" stroke="white" stroke-width="0.5"/>
</svg>

After

Width:  |  Height:  |  Size: 814 B

@ -0,0 +1,27 @@
<svg width="131" height="66" viewBox="0 0 131 66" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_4785_119799)">
<mask id="path-1-inside-1_4785_119799" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 2C7.79086 2 6 3.79086 6 6V44C6 46.2091 7.79086 48 10 48H58.8L66 56L73.2 48H121C123.209 48 125 46.2091 125 44V6C125 3.79086 123.209 2 121 2H10Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 2C7.79086 2 6 3.79086 6 6V44C6 46.2091 7.79086 48 10 48H58.8L66 56L73.2 48H121C123.209 48 125 46.2091 125 44V6C125 3.79086 123.209 2 121 2H10Z" fill="white"/>
<path d="M58.8 48L59.5433 47.331L59.2454 47H58.8V48ZM66 56L65.2567 56.669L66 57.4948L66.7433 56.669L66 56ZM73.2 48V47H72.7546L72.4567 47.331L73.2 48ZM7 6C7 4.34315 8.34315 3 10 3V1C7.23858 1 5 3.23857 5 6H7ZM7 44V6H5V44H7ZM10 47C8.34315 47 7 45.6569 7 44H5C5 46.7614 7.23858 49 10 49V47ZM58.8 47H10V49H58.8V47ZM58.0567 48.669L65.2567 56.669L66.7433 55.331L59.5433 47.331L58.0567 48.669ZM66.7433 56.669L73.9433 48.669L72.4567 47.331L65.2567 55.331L66.7433 56.669ZM121 47H73.2V49H121V47ZM124 44C124 45.6569 122.657 47 121 47V49C123.761 49 126 46.7614 126 44H124ZM124 6V44H126V6H124ZM121 3C122.657 3 124 4.34315 124 6H126C126 3.23858 123.761 1 121 1V3ZM10 3H121V1H10V3Z" fill="#154DDD" fill-opacity="0.2" mask="url(#path-1-inside-1_4785_119799)"/>
</g>
<rect x="14" y="9" width="32" height="32" rx="2" fill="#FAAD14"/>
<path d="M40.0018 16H29.5301H20.1055" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M29.5273 24.8419V16" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.8972 23.0625L38.5869 27.5048L37.6746 28.8773L35.579 32.6272L34.6667 33.9997L20 30.0959L21.8972 23.0625Z" fill="white" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M37.6737 28.8789L39.6967 29.4174L38.6126 33.4365L35.5781 32.6288" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M26.6615 27.7018C26.6615 28.8548 25.7201 29.7923 24.5554 29.7923C23.3906 29.7923 22.4492 28.8548 22.4492 27.7018C22.4492 26.5488 23.3906 25.6113 24.5554 25.6113C25.7201 25.6113 26.6615 26.5488 26.6615 27.7018Z" stroke="#FAAD14" stroke-width="0.5"/>
<defs>
<filter id="filter0_d_4785_119799" x="0" y="0" width="131" height="66" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="3"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4785_119799"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_4785_119799" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@ -0,0 +1,26 @@
<svg width="36" height="39" viewBox="0 0 36 39" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_4310_40100)">
<mask id="path-1-inside-1_4310_40100" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.2704 25.9335C25.3006 25.299 30 20.1983 30 14C30 7.37258 24.6274 2 18 2C11.3726 2 6 7.37258 6 14C6 19.8172 10.1393 24.6677 15.6332 25.7667L17.5 29L19.2704 25.9335Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.2704 25.9335C25.3006 25.299 30 20.1983 30 14C30 7.37258 24.6274 2 18 2C11.3726 2 6 7.37258 6 14C6 19.8172 10.1393 24.6677 15.6332 25.7667L17.5 29L19.2704 25.9335Z" fill="#154DDD" fill-opacity="0.05" shape-rendering="crispEdges"/>
<path d="M19.2704 25.9335L19.1658 24.939L18.6591 24.9924L18.4044 25.4335L19.2704 25.9335ZM15.6332 25.7667L16.4993 25.2667L16.273 24.8748L15.8294 24.7861L15.6332 25.7667ZM17.5 29L16.634 29.5L17.5 31L18.366 29.5L17.5 29ZM29 14C29 19.6811 24.6924 24.3575 19.1658 24.939L19.3751 26.9281C25.9088 26.2405 31 20.7155 31 14H29ZM18 3C24.0751 3 29 7.92487 29 14H31C31 6.8203 25.1797 1 18 1V3ZM7 14C7 7.92487 11.9249 3 18 3V1C10.8203 1 5 6.8203 5 14H7ZM15.8294 24.7861C10.7937 23.7788 7 19.3313 7 14H5C5 20.3032 9.48488 25.5566 15.4371 26.7472L15.8294 24.7861ZM14.7672 26.2667L16.634 29.5L18.366 28.5L16.4993 25.2667L14.7672 26.2667ZM18.366 29.5L20.1364 26.4335L18.4044 25.4335L16.634 28.5L18.366 29.5Z" fill="#154DDD" mask="url(#path-1-inside-1_4310_40100)"/>
</g>
<path d="M24.3334 8H17.6667H11.6667" stroke="#E80D0D" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.6667 13.6667V8" stroke="#E80D0D" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.8084 12.5273L23.4336 15.3744L22.8528 16.254L21.5186 18.6573L20.9379 19.5369L11.6006 17.035L12.8084 12.5273Z" fill="#E80D0D" stroke="#E80D0D" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M22.8527 16.2539L24.1406 16.599L23.4504 19.1748L21.5186 18.6572" stroke="#E80D0D" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="14.5" cy="15.5" r="1.25" stroke="white" stroke-width="0.5"/>
<defs>
<filter id="filter0_d_4310_40100" x="0" y="0" width="36" height="39" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="3"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4310_40100"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_4310_40100" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@ -0,0 +1,33 @@
<svg width="153" height="145" viewBox="0 0 153 145" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="60" y="129" width="30" height="2" fill="#F39800"/>
<rect x="76" y="132.5" width="12" height="2" transform="rotate(90 76 132.5)" fill="#F39800"/>
<line x1="75.25" y1="126" x2="75.25" y2="48" stroke="#00F0FF" stroke-width="1.5"/>
<circle cx="75" cy="130" r="5" fill="#18EFBA"/>
<path d="M69.3427 123.47C67.3037 125.072 66 127.52 66 130.264C66 135.089 70.0294 139 75 139C79.9706 139 84 135.089 84 130.264C84 127.236 82.412 124.567 80 123" stroke="white" stroke-width="2" stroke-linecap="square"/>
<g filter="url(#filter0_d_9883_75580)">
<mask id="path-6-inside-1_9883_75580" fill="white">
<path d="M6 6C6 3.79086 7.79086 2 10 2H143C145.209 2 147 3.79086 147 6V44C147 46.2091 145.209 48 143 48H10C7.79086 48 6 46.2091 6 44V6Z"/>
</mask>
<path d="M6 6C6 3.79086 7.79086 2 10 2H143C145.209 2 147 3.79086 147 6V44C147 46.2091 145.209 48 143 48H10C7.79086 48 6 46.2091 6 44V6Z" fill="#00183E" fill-opacity="0.9" shape-rendering="crispEdges"/>
<path d="M10 3.5H143V0.5H10V3.5ZM145.5 6V44H148.5V6H145.5ZM143 46.5H10V49.5H143V46.5ZM7.5 44V6H4.5V44H7.5ZM10 46.5C8.61929 46.5 7.5 45.3807 7.5 44H4.5C4.5 47.0376 6.96243 49.5 10 49.5V46.5ZM145.5 44C145.5 45.3807 144.381 46.5 143 46.5V49.5C146.038 49.5 148.5 47.0376 148.5 44H145.5ZM143 3.5C144.381 3.5 145.5 4.61929 145.5 6H148.5C148.5 2.96244 146.038 0.5 143 0.5V3.5ZM10 0.5C6.96243 0.5 4.5 2.96243 4.5 6H7.5C7.5 4.61929 8.61929 3.5 10 3.5V0.5Z" fill="#00F0FF" mask="url(#path-6-inside-1_9883_75580)"/>
</g>
<rect x="14" y="9" width="32" height="32" rx="2" fill="#52C41A"/>
<path d="M40.0018 16H29.5301H20.1055" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M29.5273 24.8419V16" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.8972 23.0625L38.5869 27.5048L37.6746 28.8773L35.579 32.6272L34.6667 33.9997L20 30.0959L21.8972 23.0625Z" fill="white" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M37.6737 28.8782L39.6967 29.4166L38.6126 33.4357L35.5781 32.6281" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M26.6615 27.7026C26.6615 28.8555 25.7201 29.793 24.5554 29.793C23.3906 29.793 22.4492 28.8555 22.4492 27.7026C22.4492 26.5496 23.3906 25.6121 24.5554 25.6121C25.7201 25.6121 26.6615 26.5496 26.6615 27.7026Z" stroke="#52C41A" stroke-width="0.5"/>
<defs>
<filter id="filter0_d_9883_75580" x="0" y="0" width="153" height="58" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="3"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_9883_75580"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_9883_75580" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

@ -0,0 +1,26 @@
<svg width="32" height="35" viewBox="0 0 32 35" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_4341_40811)">
<mask id="path-1-inside-1_4341_40811" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.2704 24.9335C23.3006 24.299 28 19.1983 28 13C28 6.37258 22.6274 1 16 1C9.37258 1 4 6.37258 4 13C4 18.8172 8.1393 23.6677 13.6332 24.7667L15.5 28L17.2704 24.9335Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.2704 24.9335C23.3006 24.299 28 19.1983 28 13C28 6.37258 22.6274 1 16 1C9.37258 1 4 6.37258 4 13C4 18.8172 8.1393 23.6677 13.6332 24.7667L15.5 28L17.2704 24.9335Z" fill="#154DDD" fill-opacity="0.05" shape-rendering="crispEdges"/>
<path d="M17.2704 24.9335L17.1658 23.939L16.6591 23.9924L16.4044 24.4335L17.2704 24.9335ZM13.6332 24.7667L14.4993 24.2667L14.273 23.8748L13.8294 23.7861L13.6332 24.7667ZM15.5 28L14.634 28.5L15.5 30L16.366 28.5L15.5 28ZM27 13C27 18.6811 22.6924 23.3575 17.1658 23.939L17.3751 25.9281C23.9088 25.2405 29 19.7155 29 13H27ZM16 2C22.0751 2 27 6.92487 27 13H29C29 5.8203 23.1797 0 16 0V2ZM5 13C5 6.92487 9.92487 2 16 2V0C8.8203 0 3 5.8203 3 13H5ZM13.8294 23.7861C8.79373 22.7788 5 18.3313 5 13H3C3 19.3032 7.48488 24.5566 13.4371 25.7472L13.8294 23.7861ZM12.7672 25.2667L14.634 28.5L16.366 27.5L14.4993 24.2667L12.7672 25.2667ZM16.366 28.5L18.1364 25.4335L16.4044 24.4335L14.634 27.5L16.366 28.5Z" fill="#154DDD" mask="url(#path-1-inside-1_4341_40811)"/>
</g>
<path d="M22.3333 7H15.6667H9.66666" stroke="#52C41A" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.6667 12.6667V7" stroke="#52C41A" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.8083 11.5273L21.4335 14.3744L20.8527 15.254L19.5186 17.6573L18.9378 18.5369L9.60049 16.035L10.8083 11.5273Z" fill="#52C41A" stroke="#52C41A" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M20.8528 15.2539L22.1407 15.599L21.4505 18.1748L19.5186 17.6572" stroke="#52C41A" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="12.5" cy="14.5" r="1.25" stroke="white" stroke-width="0.5"/>
<defs>
<filter id="filter0_d_4341_40811" x="0" y="0" width="32" height="35" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="3"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4341_40811"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_4341_40811" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@ -0,0 +1,29 @@
<svg width="153" height="145" viewBox="0 0 153 145" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="60" y="129" width="30" height="2" fill="#F39800"/>
<rect x="76" y="132.5" width="12" height="2" transform="rotate(90 76 132.5)" fill="#F39800"/>
<line x1="75.25" y1="126" x2="75.25" y2="48" stroke="#00F0FF" stroke-width="1.5"/>
<circle cx="75" cy="130" r="5" fill="#18EFBA"/>
<path d="M69.3427 123.47C67.3037 125.072 66 127.52 66 130.264C66 135.089 70.0294 139 75 139C79.9706 139 84 135.089 84 130.264C84 127.236 82.412 124.567 80 123" stroke="white" stroke-width="2" stroke-linecap="square"/>
<g filter="url(#filter0_d_10037_91857)">
<path d="M6 6C6 3.79086 7.79086 2 10 2H143C145.209 2 147 3.79086 147 6V44C147 46.2091 145.209 48 143 48H10C7.79086 48 6 46.2091 6 44V6Z" fill="#00183E" fill-opacity="0.9" shape-rendering="crispEdges"/>
</g>
<rect x="14" y="9" width="32" height="32" rx="2" fill="#52C41A"/>
<path d="M40.0018 16H29.5301H20.1055" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M29.5273 24.8419V16" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.8972 23.0625L38.5869 27.5048L37.6746 28.8773L35.579 32.6272L34.6667 33.9997L20 30.0959L21.8972 23.0625Z" fill="white" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M37.6737 28.8782L39.6967 29.4166L38.6126 33.4357L35.5781 32.6281" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M26.6615 27.7026C26.6615 28.8555 25.7201 29.793 24.5554 29.793C23.3906 29.793 22.4492 28.8555 22.4492 27.7026C22.4492 26.5496 23.3906 25.6121 24.5554 25.6121C25.7201 25.6121 26.6615 26.5496 26.6615 27.7026Z" stroke="#52C41A" stroke-width="0.5"/>
<defs>
<filter id="filter0_d_10037_91857" x="0" y="0" width="153" height="58" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="3"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_10037_91857"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_10037_91857" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

@ -0,0 +1,26 @@
<svg width="36" height="39" viewBox="0 0 36 39" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_4310_40080)">
<mask id="path-1-inside-1_4310_40080" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.2704 25.9335C25.3006 25.299 30 20.1983 30 14C30 7.37258 24.6274 2 18 2C11.3726 2 6 7.37258 6 14C6 19.8172 10.1393 24.6677 15.6332 25.7667L17.5 29L19.2704 25.9335Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.2704 25.9335C25.3006 25.299 30 20.1983 30 14C30 7.37258 24.6274 2 18 2C11.3726 2 6 7.37258 6 14C6 19.8172 10.1393 24.6677 15.6332 25.7667L17.5 29L19.2704 25.9335Z" fill="#154DDD" fill-opacity="0.05" shape-rendering="crispEdges"/>
<path d="M19.2704 25.9335L19.1658 24.939L18.6591 24.9924L18.4044 25.4335L19.2704 25.9335ZM15.6332 25.7667L16.4993 25.2667L16.273 24.8748L15.8294 24.7861L15.6332 25.7667ZM17.5 29L16.634 29.5L17.5 31L18.366 29.5L17.5 29ZM29 14C29 19.6811 24.6924 24.3575 19.1658 24.939L19.3751 26.9281C25.9088 26.2405 31 20.7155 31 14H29ZM18 3C24.0751 3 29 7.92487 29 14H31C31 6.8203 25.1797 1 18 1V3ZM7 14C7 7.92487 11.9249 3 18 3V1C10.8203 1 5 6.8203 5 14H7ZM15.8294 24.7861C10.7937 23.7788 7 19.3313 7 14H5C5 20.3032 9.48488 25.5566 15.4371 26.7472L15.8294 24.7861ZM14.7672 26.2667L16.634 29.5L18.366 28.5L16.4993 25.2667L14.7672 26.2667ZM18.366 29.5L20.1364 26.4335L18.4044 25.4335L16.634 28.5L18.366 29.5Z" fill="#154DDD" mask="url(#path-1-inside-1_4310_40080)"/>
</g>
<path d="M24.3333 8H17.6666H11.6666" stroke="#CCCCCC" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.6666 13.6667V8" stroke="#CCCCCC" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.8083 12.5273L23.4335 15.3744L22.8527 16.254L21.5185 18.6573L20.9377 19.5369L11.6005 17.035L12.8083 12.5273Z" fill="#CCCCCC" stroke="#CCCCCC" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M22.8528 16.2539L24.1407 16.599L23.4505 19.1748L21.5187 18.6572" stroke="#CCCCCC" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="14.5" cy="15.5" r="1.25" stroke="white" stroke-width="0.5"/>
<defs>
<filter id="filter0_d_4310_40080" x="0" y="0" width="36" height="39" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="3"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4310_40080"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_4310_40080" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@ -0,0 +1,27 @@
<svg width="131" height="66" viewBox="0 0 131 66" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_4785_117861)">
<mask id="path-1-inside-1_4785_117861" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 2C7.79086 2 6 3.79086 6 6V44C6 46.2091 7.79086 48 10 48H58.8L66 56L73.2 48H121C123.209 48 125 46.2091 125 44V6C125 3.79086 123.209 2 121 2H10Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 2C7.79086 2 6 3.79086 6 6V44C6 46.2091 7.79086 48 10 48H58.8L66 56L73.2 48H121C123.209 48 125 46.2091 125 44V6C125 3.79086 123.209 2 121 2H10Z" fill="white"/>
<path d="M58.8 48L59.5433 47.331L59.2454 47H58.8V48ZM66 56L65.2567 56.669L66 57.4948L66.7433 56.669L66 56ZM73.2 48V47H72.7546L72.4567 47.331L73.2 48ZM7 6C7 4.34315 8.34315 3 10 3V1C7.23858 1 5 3.23857 5 6H7ZM7 44V6H5V44H7ZM10 47C8.34315 47 7 45.6569 7 44H5C5 46.7614 7.23858 49 10 49V47ZM58.8 47H10V49H58.8V47ZM58.0567 48.669L65.2567 56.669L66.7433 55.331L59.5433 47.331L58.0567 48.669ZM66.7433 56.669L73.9433 48.669L72.4567 47.331L65.2567 55.331L66.7433 56.669ZM121 47H73.2V49H121V47ZM124 44C124 45.6569 122.657 47 121 47V49C123.761 49 126 46.7614 126 44H124ZM124 6V44H126V6H124ZM121 3C122.657 3 124 4.34315 124 6H126C126 3.23858 123.761 1 121 1V3ZM10 3H121V1H10V3Z" fill="#154DDD" fill-opacity="0.2" mask="url(#path-1-inside-1_4785_117861)"/>
</g>
<rect x="14" y="9" width="32" height="32" rx="2" fill="#E80D0D"/>
<path d="M40.0018 16H29.5301H20.1055" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M29.5273 24.8419V16" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.8972 23.0625L38.5869 27.5048L37.6746 28.8773L35.579 32.6272L34.6667 33.9997L20 30.0959L21.8972 23.0625Z" fill="white" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M37.6737 28.8789L39.6967 29.4174L38.6126 33.4365L35.5781 32.6288" stroke="white" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M26.6615 27.7018C26.6615 28.8548 25.7201 29.7923 24.5554 29.7923C23.3906 29.7923 22.4492 28.8548 22.4492 27.7018C22.4492 26.5488 23.3906 25.6113 24.5554 25.6113C25.7201 25.6113 26.6615 26.5488 26.6615 27.7018Z" stroke="#E80D0D" stroke-width="0.5"/>
<defs>
<filter id="filter0_d_4785_117861" x="0" y="0" width="131" height="66" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="3"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4785_117861"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_4785_117861" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@ -0,0 +1,26 @@
<svg width="36" height="39" viewBox="0 0 36 39" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_4517_70725)">
<mask id="path-1-inside-1_4517_70725" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.2704 25.9335C25.3006 25.299 30 20.1983 30 14C30 7.37258 24.6274 2 18 2C11.3726 2 6 7.37258 6 14C6 19.8172 10.1393 24.6677 15.6332 25.7667L17.5 29L19.2704 25.9335Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.2704 25.9335C25.3006 25.299 30 20.1983 30 14C30 7.37258 24.6274 2 18 2C11.3726 2 6 7.37258 6 14C6 19.8172 10.1393 24.6677 15.6332 25.7667L17.5 29L19.2704 25.9335Z" fill="#154DDD" fill-opacity="0.05" shape-rendering="crispEdges"/>
<path d="M19.2704 25.9335L19.1658 24.939L18.6591 24.9924L18.4044 25.4335L19.2704 25.9335ZM15.6332 25.7667L16.4993 25.2667L16.273 24.8748L15.8294 24.7861L15.6332 25.7667ZM17.5 29L16.634 29.5L17.5 31L18.366 29.5L17.5 29ZM29 14C29 19.6811 24.6924 24.3575 19.1658 24.939L19.3751 26.9281C25.9088 26.2405 31 20.7155 31 14H29ZM18 3C24.0751 3 29 7.92487 29 14H31C31 6.8203 25.1797 1 18 1V3ZM7 14C7 7.92487 11.9249 3 18 3V1C10.8203 1 5 6.8203 5 14H7ZM15.8294 24.7861C10.7937 23.7788 7 19.3313 7 14H5C5 20.3032 9.48488 25.5566 15.4371 26.7472L15.8294 24.7861ZM14.7672 26.2667L16.634 29.5L18.366 28.5L16.4993 25.2667L14.7672 26.2667ZM18.366 29.5L20.1364 26.4335L18.4044 25.4335L16.634 28.5L18.366 29.5Z" fill="#154DDD" mask="url(#path-1-inside-1_4517_70725)"/>
</g>
<path d="M24.3333 8H17.6666H11.6666" stroke="#FAAD14" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.6666 13.6667V8" stroke="#FAAD14" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.8083 12.5273L23.4335 15.3744L22.8527 16.254L21.5185 18.6573L20.9377 19.5369L11.6005 17.035L12.8083 12.5273Z" fill="#FAAD14" stroke="#FAAD14" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M22.8528 16.2539L24.1407 16.599L23.4505 19.1748L21.5187 18.6572" stroke="#FAAD14" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="14.5" cy="15.5" r="1.25" stroke="white" stroke-width="0.5"/>
<defs>
<filter id="filter0_d_4517_70725" x="0" y="0" width="36" height="39" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="3"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4517_70725"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_4517_70725" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" width="131" height="66" viewBox="0 0 131 66" fill="none">
<g filter="url(#filter0_d_4785_117771)">
<mask id="path-1-inside-1_4785_117771" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 2C7.79086 2 6 3.79086 6 6V44C6 46.2091 7.79086 48 10 48H58.8L66 56L73.2 48H121C123.209 48 125 46.2091 125 44V6C125 3.79086 123.209 2 121 2H10Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 2C7.79086 2 6 3.79086 6 6V44C6 46.2091 7.79086 48 10 48H58.8L66 56L73.2 48H121C123.209 48 125 46.2091 125 44V6C125 3.79086 123.209 2 121 2H10Z" fill="white"/>
<path d="M58.8 48L59.5433 47.331L59.2454 47H58.8V48ZM66 56L65.2567 56.669L66 57.4948L66.7433 56.669L66 56ZM73.2 48V47H72.7546L72.4567 47.331L73.2 48ZM7 6C7 4.34315 8.34315 3 10 3V1C7.23858 1 5 3.23857 5 6H7ZM7 44V6H5V44H7ZM10 47C8.34315 47 7 45.6569 7 44H5C5 46.7614 7.23858 49 10 49V47ZM58.8 47H10V49H58.8V47ZM58.0567 48.669L65.2567 56.669L66.7433 55.331L59.5433 47.331L58.0567 48.669ZM66.7433 56.669L73.9433 48.669L72.4567 47.331L65.2567 55.331L66.7433 56.669ZM121 47H73.2V49H121V47ZM124 44C124 45.6569 122.657 47 121 47V49C123.761 49 126 46.7614 126 44H124ZM124 6V44H126V6H124ZM121 3C122.657 3 124 4.34315 124 6H126C126 3.23858 123.761 1 121 1V3ZM10 3H121V1H10V3Z" fill="#154DDD" fill-opacity="0.2" mask="url(#path-1-inside-1_4785_117771)"/>
</g>
<defs>
<filter id="filter0_d_4785_117771" x="0" y="0" width="131" height="66" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="3"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_4785_117771"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_4785_117771" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@ -0,0 +1,27 @@
// 动效类型
export const animationTypeConf: Record<string, any>[] = [
{
value: "None",
label: "None"
},
{
value: "Fade",
label: "Fade"
},
{
value: "Bounce",
label: "Bounce"
},
{
value: "Shake",
label: "Shake"
},
{
value: "Rotation",
label: "Rotation"
},
{
value: "Flash",
label: "Flash"
}
];

@ -0,0 +1,103 @@
// 通用元素
export const baseTypeConf: string[] = [
"text",
"i-text",
"textbox",
"rect",
"circle",
"triangle",
"polygon",
"image",
"group",
"line",
"arrow"
];
// 文字元素
export const textTypeConf: string[] = ["i-text", "textbox", "text"];
// 字体下拉列表
export const strokeDashListConf: Record<string, any>[] = [
{
value: {
strokeUniform: true,
strokeDashArray: [],
strokeLineCap: "butt"
},
label: "Stroke"
},
{
value: {
strokeUniform: true,
strokeDashArray: [1, 10],
strokeLineCap: "butt"
},
label: "Dash-1"
},
{
value: {
strokeUniform: true,
strokeDashArray: [1, 10],
strokeLineCap: "round"
},
label: "Dash-2"
},
{
value: {
strokeUniform: true,
strokeDashArray: [15, 15],
strokeLineCap: "square"
},
label: "Dash-3"
},
{
value: {
strokeUniform: true,
strokeDashArray: [15, 15],
strokeLineCap: "round"
},
label: "Dash-4"
},
{
value: {
strokeUniform: true,
strokeDashArray: [25, 25],
strokeLineCap: "square"
},
label: "Dash-5"
},
{
value: {
strokeUniform: true,
strokeDashArray: [25, 25],
strokeLineCap: "round"
},
label: "Dash-6"
},
{
value: {
strokeUniform: true,
strokeDashArray: [1, 8, 16, 8, 1, 20],
strokeLineCap: "square"
},
label: "Dash-7"
},
{
value: {
strokeUniform: true,
strokeDashArray: [1, 8, 16, 8, 1, 20],
strokeLineCap: "round"
},
label: "Dash-8"
}
];
// 对齐图标
export const textAlignListSvgConf: string[] = [
'<svg t="1650441458823" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3554" width="18" height="18"><path d="M198.4 198.4h341.333333c8.533333 0 14.933333 2.133333 19.2 8.533333 6.4 6.4 8.533333 12.8 8.533334 19.2v57.6c0 8.533333-2.133333 14.933333-8.533334 19.2-6.4 6.4-12.8 8.533333-19.2 8.533334h-341.333333c-8.533333 0-14.933333-2.133333-19.2-8.533334-6.4-6.4-8.533333-12.8-8.533333-19.2v-57.6c0-8.533333 2.133333-14.933333 8.533333-19.2 4.266667-4.266667 12.8-8.533333 19.2-8.533333z m0 170.666667h569.6c8.533333 0 14.933333 2.133333 19.2 8.533333 6.4 6.4 8.533333 12.8 8.533333 19.2v57.6c0 8.533333-2.133333 14.933333-8.533333 19.2-6.4 6.4-12.8 8.533333-19.2 8.533333h-569.6c-8.533333 0-14.933333-2.133333-19.2-8.533333-6.4-6.4-8.533333-12.8-8.533333-19.2v-57.6c0-8.533333 2.133333-14.933333 8.533333-19.2 4.266667-4.266667 12.8-8.533333 19.2-8.533333z m0 170.666666h454.4c8.533333 0 14.933333 2.133333 19.2 8.533334 6.4 6.4 8.533333 12.8 8.533333 19.2v57.6c0 8.533333-2.133333 14.933333-8.533333 19.2-6.4 6.4-12.8 8.533333-19.2 8.533333h-454.4c-8.533333 0-14.933333-2.133333-19.2-8.533333-6.4-6.4-8.533333-12.8-8.533333-19.2v-57.6c0-8.533333 2.133333-14.933333 8.533333-19.2 4.266667-4.266667 12.8-8.533333 19.2-8.533334z m0 170.666667h625.066667c8.533333 0 14.933333 2.133333 19.2 8.533333 6.4 6.4 8.533333 12.8 8.533333 19.2v57.6c0 8.533333-2.133333 14.933333-8.533333 19.2-6.4 6.4-12.8 8.533333-19.2 8.533334h-625.066667c-8.533333 0-14.933333-2.133333-19.2-8.533334-6.4-6.4-8.533333-12.8-8.533333-19.2v-57.6c0-8.533333 2.133333-14.933333 8.533333-19.2 4.266667-4.266667 12.8-8.533333 19.2-8.533333z" p-id="3555"></path></svg>',
'<svg t="1650441512015" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3704" width="18" height="18"><path d="M313.6 198.4h398.933333c8.533333 0 14.933333 2.133333 19.2 8.533333 6.4 6.4 8.533333 12.8 8.533334 19.2v57.6c0 8.533333-2.133333 14.933333-8.533334 19.2-6.4 6.4-12.8 8.533333-19.2 8.533334h-398.933333c-8.533333 0-14.933333-2.133333-19.2-8.533334-6.4-6.4-8.533333-12.8-8.533333-19.2v-57.6c0-8.533333 2.133333-14.933333 8.533333-19.2 4.266667-4.266667 10.666667-8.533333 19.2-8.533333z m-115.2 170.666667h625.066667c8.533333 0 14.933333 2.133333 19.2 8.533333 6.4 6.4 8.533333 12.8 8.533333 19.2v57.6c0 8.533333-2.133333 14.933333-8.533333 19.2-6.4 6.4-12.8 8.533333-19.2 8.533333h-625.066667c-8.533333 0-14.933333-2.133333-19.2-8.533333-6.4-6.4-8.533333-12.8-8.533333-19.2v-57.6c0-8.533333 2.133333-14.933333 8.533333-19.2 4.266667-4.266667 12.8-8.533333 19.2-8.533333z m115.2 170.666666h398.933333c8.533333 0 14.933333 2.133333 19.2 8.533334 6.4 6.4 8.533333 12.8 8.533334 19.2v57.6c0 8.533333-2.133333 14.933333-8.533334 19.2-6.4 6.4-12.8 8.533333-19.2 8.533333h-398.933333c-8.533333 0-14.933333-2.133333-19.2-8.533333-6.4-6.4-8.533333-12.8-8.533333-19.2v-57.6c0-8.533333 2.133333-14.933333 8.533333-19.2 4.266667-4.266667 10.666667-8.533333 19.2-8.533334z m-115.2 170.666667h625.066667c8.533333 0 14.933333 2.133333 19.2 8.533333 6.4 6.4 8.533333 12.8 8.533333 19.2v57.6c0 8.533333-2.133333 14.933333-8.533333 19.2-6.4 6.4-12.8 8.533333-19.2 8.533334h-625.066667c-8.533333 0-14.933333-2.133333-19.2-8.533334-6.4-6.4-8.533333-12.8-8.533333-19.2v-57.6c0-8.533333 2.133333-14.933333 8.533333-19.2 4.266667-4.266667 12.8-8.533333 19.2-8.533333z" p-id="3705"></path></svg>',
'<svg t="1650441519862" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3854" width="18" height="18"><path d="M454.4 283.733333v-57.6c0-8.533333 2.133333-14.933333 8.533333-19.2 6.4-6.4 12.8-8.533333 19.2-8.533333h341.333334c8.533333 0 14.933333 2.133333 19.2 8.533333 6.4 6.4 8.533333 12.8 8.533333 19.2v57.6c0 8.533333-2.133333 14.933333-8.533333 19.2-6.4 6.4-12.8 8.533333-19.2 8.533334h-341.333334c-8.533333 0-14.933333-2.133333-19.2-8.533334-4.266667-4.266667-8.533333-10.666667-8.533333-19.2z m-226.133333 170.666667v-57.6c0-8.533333 2.133333-14.933333 8.533333-19.2 6.4-6.4 12.8-8.533333 19.2-8.533333h569.6c8.533333 0 14.933333 2.133333 19.2 8.533333 6.4 6.4 8.533333 12.8 8.533333 19.2v57.6c0 8.533333-2.133333 14.933333-8.533333 19.2-6.4 6.4-12.8 8.533333-19.2 8.533333H256c-8.533333 0-14.933333-2.133333-19.2-8.533333-6.4-4.266667-8.533333-10.666667-8.533333-19.2z m113.066666 170.666667v-57.6c0-8.533333 2.133333-14.933333 8.533334-19.2 6.4-6.4 12.8-8.533333 19.2-8.533334h454.4c8.533333 0 14.933333 2.133333 19.2 8.533334 6.4 6.4 8.533333 12.8 8.533333 19.2v57.6c0 8.533333-2.133333 14.933333-8.533333 19.2-6.4 6.4-12.8 8.533333-19.2 8.533333h-454.4c-8.533333 0-14.933333-2.133333-19.2-8.533333-6.4-4.266667-8.533333-10.666667-8.533334-19.2z m-170.666666 170.666666v-57.6c0-8.533333 2.133333-14.933333 8.533333-19.2 6.4-6.4 12.8-8.533333 19.2-8.533333h625.066667c8.533333 0 14.933333 2.133333 19.2 8.533333 6.4 6.4 8.533333 12.8 8.533333 19.2v57.6c0 8.533333-2.133333 14.933333-8.533333 19.2-6.4 6.4-12.8 8.533333-19.2 8.533334h-625.066667c-8.533333 0-14.933333-2.133333-19.2-8.533334-6.4-4.266667-8.533333-10.666667-8.533333-19.2z" p-id="3855"></path></svg>'
];
// 字体对齐方式
export const textAlignListConf: string[] = ["left", "center", "right"];

@ -0,0 +1 @@
export const LANG = "lang"; // 多语言key

@ -0,0 +1,196 @@
// UI类型
export const uiType = {
SELECT: "select",
COLOR: "color",
NUMBER: "number"
};
// 有参数滤镜
export const paramsFilters = [
{
type: "Brightness",
status: false,
params: [
{
key: "brightness",
value: 0,
uiType: uiType.NUMBER,
min: -1,
max: 1,
step: 0.01
}
]
},
{
type: "Contrast",
status: false,
params: [
{
key: "contrast",
value: 0,
uiType: uiType.NUMBER,
min: -1,
max: 1,
step: 0.01
}
]
},
{
type: "Saturation",
status: false,
params: [
{
key: "saturation",
value: 0,
uiType: uiType.NUMBER,
min: -1,
max: 1,
step: 0.01
}
]
},
{
type: "Vibrance",
status: false,
params: [
{
key: "vibrance",
value: 0,
uiType: uiType.NUMBER,
min: -1,
max: 1,
step: 0.01
}
]
},
{
type: "HueRotation",
status: false,
params: [
{
key: "rotation",
value: 0,
uiType: uiType.NUMBER,
min: -1,
max: 1,
step: 0.01
}
]
},
{
type: "Noise",
status: false,
params: [
{
key: "noise",
value: 0,
uiType: uiType.NUMBER,
min: -1,
max: 1000,
step: 0.1
}
]
},
{
type: "Pixelate",
status: false,
params: [
{
key: "blocksize",
value: 0.01,
uiType: uiType.NUMBER,
min: 0.01,
max: 100,
step: 0.01
}
]
},
{
type: "Blur",
status: false,
params: [
{
key: "blur",
value: 0,
uiType: uiType.NUMBER,
min: 0,
max: 1,
step: 0.01
}
]
},
{
type: "Grayscale",
status: false,
params: [
{
key: "mode",
value: "average",
uiType: uiType.SELECT,
list: ["average", "lightness", "luminosity"]
}
]
},
{
type: "RemoveColor",
status: false,
params: [
{
key: "color",
value: "",
uiType: uiType.COLOR
},
{
key: "distance",
value: 0,
uiType: uiType.NUMBER,
min: 0,
max: 1,
step: 0.01
}
]
}
];
// 组合式参数滤镜
export const combinationFilters = [
{
type: "Gamma",
status: false,
params: [
{
key: "red",
value: 0,
uiType: uiType.NUMBER,
min: 0.01,
max: 2.2,
step: 0.01
},
{
key: "green",
value: 0,
uiType: uiType.NUMBER,
min: 0.01,
max: 2.2,
step: 0.01
},
{
key: "blue",
value: 0,
uiType: uiType.NUMBER,
min: 0.01,
max: 2.2,
step: 0.01
}
],
handler(
red: number | string,
green: number | string,
blue: number | string
) {
return {
gamma: [red, green, blue]
};
}
}
];

@ -0,0 +1,270 @@
/* eslint-disable no-prototype-builtins */
/*
* @Author: 秦少卫
* @Date: 2023-05-25 22:33:23
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:31:16
* @Description: 右键菜单
*/
class ContextMenu {
constructor(container, items) {
this.container = container;
this.dom = null;
this.shown = false;
this.root = true;
this.parent = null;
this.submenus = [];
this.items = items;
this._onclick = e => {
if (
this.dom &&
e.target != this.dom &&
e.target.parentElement != this.dom &&
!e.target.classList.contains("item") &&
!e.target.parentElement.classList.contains("item")
) {
this.hideAll();
}
};
this._oncontextmenu = e => {
e.preventDefault();
if (
e.target != this.dom &&
e.target.parentElement != this.dom &&
!e.target.classList.contains("item") &&
!e.target.parentElement.classList.contains("item")
) {
this.hideAll();
this.show(e.clientX, e.clientY);
}
};
this._oncontextmenu_keydown = e => {
if (e.keyCode != 93) return;
e.preventDefault();
this.hideAll();
this.show(e.clientX, e.clientY);
};
this._onblur = () => {
this.hideAll();
};
}
getMenuDom() {
const menu = document.createElement("div");
menu.classList.add("context");
for (const item of this.items) {
menu.appendChild(this.itemToDomEl(item));
}
return menu;
}
itemToDomEl(data) {
const item = document.createElement("div");
if (data === null) {
item.classList = "separator";
return item;
}
if (
data.hasOwnProperty("color") &&
/^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(data.color.toString())
) {
item.style.cssText = `color: ${data.color}`;
}
item.classList.add("item");
const label = document.createElement("span");
label.classList = "label";
label.innerText = data.hasOwnProperty("text")
? data["text"].toString()
: "";
item.appendChild(label);
if (data.hasOwnProperty("disabled") && data["disabled"]) {
item.classList.add("disabled");
} else {
item.classList.add("enabled");
}
const hotkey = document.createElement("span");
hotkey.classList = "hotkey";
hotkey.innerText = data.hasOwnProperty("hotkey")
? data["hotkey"].toString()
: "";
item.appendChild(hotkey);
if (
data.hasOwnProperty("subitems") &&
Array.isArray(data["subitems"]) &&
data["subitems"].length > 0
) {
const menu = new ContextMenu(this.container, data["subitems"]);
menu.root = false;
menu.parent = this;
const openSubItems = () => {
if (data.hasOwnProperty("disabled") && data["disabled"] == true) return;
this.hideSubMenus();
const x = this.dom.offsetLeft + this.dom.clientWidth + item.offsetLeft;
const y = this.dom.offsetTop + item.offsetTop;
if (!menu.shown) {
menu.show(x, y);
} else {
menu.hide();
}
};
this.submenus.push(menu);
item.classList.add("has-subitems");
item.addEventListener("click", openSubItems);
item.addEventListener("mousemove", openSubItems);
} else if (
data.hasOwnProperty("submenu") &&
data["submenu"] instanceof ContextMenu
) {
const menu = data["submenu"];
menu.root = false;
menu.parent = this;
const openSubItems = () => {
if (data.hasOwnProperty("disabled") && data["disabled"] == true) return;
this.hideSubMenus();
const x = this.dom.offsetLeft + this.dom.clientWidth + item.offsetLeft;
const y = this.dom.offsetTop + item.offsetTop;
if (!menu.shown) {
menu.show(x, y);
} else {
menu.hide();
}
};
this.submenus.push(menu);
item.classList.add("has-subitems");
item.addEventListener("click", openSubItems);
item.addEventListener("mousemove", openSubItems);
} else {
item.addEventListener("click", () => {
this.hideSubMenus();
if (item.classList.contains("disabled")) return;
if (
data.hasOwnProperty("onclick") &&
typeof data["onclick"] === "function"
) {
const event = {
handled: false,
item: item,
label: label,
hotkey: hotkey,
items: this.items,
data: data
};
data["onclick"](event);
if (!event.handled) {
this.hide();
}
} else {
this.hide();
}
});
item.addEventListener("mousemove", () => {
this.hideSubMenus();
});
}
return item;
}
hideAll() {
if (this.root && !this.parent) {
if (this.shown) {
this.hideSubMenus();
this.shown = false;
this.container.removeChild(this.dom);
if (this.parent && this.parent.shown) {
this.parent.hide();
}
}
return;
}
this.parent.hide();
}
hide() {
if (this.dom && this.shown) {
this.shown = false;
this.hideSubMenus();
this.container.removeChild(this.dom);
if (this.parent && this.parent.shown) {
this.parent.hide();
}
}
}
hideSubMenus() {
for (const menu of this.submenus) {
if (menu.shown) {
menu.shown = false;
menu.container.removeChild(menu.dom);
}
menu.hideSubMenus();
}
}
show(x, y) {
this.dom = this.getMenuDom();
this.dom.style.left = `${x}px`;
this.dom.style.top = `${y}px`;
this.shown = true;
this.container.appendChild(this.dom);
}
install() {
this.container.addEventListener("contextmenu", this._oncontextmenu);
this.container.addEventListener("keydown", this._oncontextmenu_keydown);
this.container.addEventListener("click", this._onclick);
window.addEventListener("blur", this._onblur);
}
setData(data) {
this.items = data;
}
uninstall() {
this.dom = null;
// this.container.removeEventListener('contextmenu', this._oncontextmenu);
this.container.removeEventListener("keydown", this._oncontextmenu_keydown);
this.container.removeEventListener("click", this._onclick);
window.removeEventListener("blur", this._onblur);
}
}
export default ContextMenu;

@ -0,0 +1,258 @@
/*
* @Author: zhoux zhouxia@supervision.ltd
* @Date: 2023-11-29 09:31:35
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:12:48
* @FilePath: \vue-fabric-editor\src\core\ServersPlugin.ts
* @Description:
*/
import { v4 as uuid } from "uuid";
import { selectFiles, clipboardText } from "@/utils/utils";
// import { clipboardText } from '@/utils/utils.ts';
import { fabric } from "fabric";
import Editor from "../core";
import { customJsonAttr } from "@/hooks/config";
type IEditor = Editor;
// import { v4 as uuid } from 'uuid';
function downFile(fileStr: string, fileType: string) {
const anchorEl = document.createElement("a");
anchorEl.href = fileStr;
anchorEl.download = `${uuid()}.${fileType}`;
document.body.appendChild(anchorEl); // required for firefox
anchorEl.click();
anchorEl.remove();
}
function transformText(objects) {
if (!objects) return;
objects.forEach(item => {
if (item.objects) {
transformText(item.objects);
} else {
item.type === "text" && (item.type = "textbox");
}
});
}
class ServersPlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
static pluginName = "ServersPlugin";
public elementHandler: ElementHandler;
static apis = [
"insert",
"insertSvgFile",
"getJson",
"dragAddItem",
"clipboard",
"saveJson",
"saveSvg",
"saveImg",
"clear",
"preview"
];
// public hotkeys: string[] = ['left', 'right', 'down', 'up'];
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
}
insert() {
selectFiles({ accept: ".json" }).then(files => {
const [file] = files;
const reader = new FileReader();
reader.readAsText(file, "UTF-8");
reader.onload = () => {
this.insertSvgFile(reader.result);
};
});
}
insertSvgFile(jsonFile) {
console.log(jsonFile, "insertSvgFile");
// 加载前钩子
this.editor.hooksEntity.hookImportBefore.callAsync(jsonFile, () => {
this.canvas.loadFromJSON(jsonFile, () => {
this.canvas.renderAll();
// 加载后钩子
this.editor.hooksEntity.hookImportAfter.callAsync(jsonFile, () => {
this.canvas.renderAll();
});
});
});
}
/**
* Set partial by object
* @param {FabricObject} obj
* @param {FabricObjectOption} option
* @returns
*/
// setByPartial(obj: FabricObject, option: FabricObjectOption) {
// if (!obj) {
// return;
// }
// if (obj.type === 'svg') {
// if (option.fill) {
// obj.setFill(option.fill);
// } else if (option.stroke) {
// obj.setStroke(option.stroke);
// }
// }
// obj.set(option);
// obj.setCoords();
// this.canvas.renderAll();
// const { id, superType, type, player, width, height } = obj as any;
// if (superType === 'element') {
// if ('visible' in option) {
// if (option.visible) {
// obj.element.style.display = 'block';
// } else {
// obj.element.style.display = 'none';
// }
// }
// const el = this.elementHandler.findById(id);
// // update the element
// this.elementHandler.setScaleOrAngle(el, obj);
// this.elementHandler.setSize(el, obj);
// this.elementHandler.setPosition(el, obj);
// if (type === 'video' && player) {
// player.setPlayerSize(width, height);
// }
// }
// }
getJson() {
// update 在json对象中新增的属性需要在此备注
return this.canvas.toJSON([...customJsonAttr]);
}
/**
* @description:
* @param {Event} event
* @param {Object} item
*/
dragAddItem(event: DragEvent, item: fabric.Object) {
const { left, top } = this.canvas
.getSelectionElement()
.getBoundingClientRect();
if (event.x < left || event.y < top || item.width === undefined) return;
const point = {
x: event.x - left,
y: event.y - top
};
const pointerVpt = this.canvas.restorePointerVpt(point);
item.left = pointerVpt.x - item.width / 2;
item.top = pointerVpt.y;
this.canvas.add(item);
this.canvas.requestRenderAll();
}
clipboard() {
const jsonStr = this.getJson();
clipboardText(JSON.stringify(jsonStr, null, "\t"));
}
async saveJson() {
const dataUrl = this.getJson();
console.log(dataUrl, "saveJson_dataUrl");
// 把文本text转为textgroup让导入可以编辑
await transformText(dataUrl.objects);
const fileStr = `data:text/json;charset=utf-8,${encodeURIComponent(
JSON.stringify({ ...dataUrl }, null, "\t")
)}`;
downFile(fileStr, "json");
}
saveSvg() {
this.editor.hooksEntity.hookSaveBefore.callAsync("", () => {
const option = this._getSaveSvgOption();
const dataUrl = this.canvas.toSVG(option);
const fileStr = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(
dataUrl
)}`;
this.editor.hooksEntity.hookSaveAfter.callAsync(fileStr, () => {
downFile(fileStr, "svg");
});
});
}
saveImg() {
this.editor.hooksEntity.hookSaveBefore.callAsync("", () => {
const option = this._getSaveOption();
this.canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
const dataUrl = this.canvas.toDataURL(option);
this.editor.hooksEntity.hookSaveAfter.callAsync(dataUrl, () => {
downFile(dataUrl, "png");
});
});
}
preview() {
return new Promise((resolve, _) => {
this.editor.hooksEntity.hookSaveBefore.callAsync("", () => {
const option = this._getSaveOption();
this.canvas.setViewportTransform([1, 0, 0, 1, 0, 0]);
this.canvas.renderAll();
const dataUrl = this.canvas.toDataURL(option);
this.editor.hooksEntity.hookSaveAfter.callAsync(dataUrl, () => {
resolve(dataUrl);
});
});
});
}
_getSaveSvgOption() {
const workspace = this.canvas
.getObjects()
.find(item => item.id === "workspace");
const { left, top, width, height } = workspace;
return {
width,
height,
viewBox: {
x: left,
y: top,
width,
height
}
};
}
_getSaveOption() {
const workspace = this.canvas
.getObjects()
.find((item: fabric.Object) => item.id === "workspace");
const { left, top, width, height } = workspace as fabric.Object;
const option = {
name: "New Image",
format: "png",
quality: 1,
width,
height,
left,
top
};
return option;
}
clear() {
this.canvas.getObjects().forEach(obj => {
if (obj.id !== "workspace") {
this.canvas.remove(obj);
}
});
this.canvas.discardActiveObject();
this.canvas.renderAll();
}
destroy() {
console.log("pluginDestroy");
}
}
export default ServersPlugin;

@ -0,0 +1,169 @@
import EventEmitter from "events";
import hotkeys from "hotkeys-js";
import ContextMenu from "./ContextMenu.js";
import ServersPlugin from "./ServersPlugin";
import { AsyncSeriesHook } from "tapable";
class Editor extends EventEmitter {
canvas: fabric.Canvas;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
contextMenu: any;
private pluginMap: {
[propName: string]: IPluginTempl;
} = {};
// 自定义事件
private customEvents: string[] = [];
// 自定义API
private customApis: string[] = [];
// 生命周期函数名
private hooks: IEditorHooksType[] = [
"hookImportBefore",
"hookImportAfter",
"hookSaveBefore",
"hookSaveAfter"
];
private hooksEntity: {
[propName: string]: AsyncSeriesHook;
} = {};
// constructor(canvas: fabric.Canvas) {
// super();
// this.canvas = canvas;
// this._initContextMenu();
// this._bindContextMenu();
// this._initActionHooks();
// this._initServersPlugin();
// }
init(canvas: fabric.Canvas) {
this.canvas = canvas;
this._initContextMenu();
this._bindContextMenu();
this._initActionHooks();
this._initServersPlugin();
}
// 引入组件
use(plugin: IPluginClass, options: IPluginOption) {
if (this._checkPlugin(plugin)) {
this._saveCustomAttr(plugin);
const pluginRunTime = new plugin(this.canvas, this, options);
this.pluginMap[plugin.pluginName] = pluginRunTime;
this._bindingHooks(pluginRunTime);
this._bindingHotkeys(pluginRunTime);
this._bindingApis(pluginRunTime);
}
}
// 获取插件
getPlugin(name: string) {
if (this.pluginMap[name]) {
return this.pluginMap[name];
}
}
// 检查组件
private _checkPlugin(plugin: IPluginClass) {
const { pluginName, events = [], apis = [] } = plugin;
//名称检查
if (this.pluginMap[pluginName]) {
throw new Error(pluginName + "插件重复初始化");
}
events.forEach((eventName: string) => {
if (this.customEvents.find(info => info === eventName)) {
throw new Error(pluginName + "插件中" + eventName + "重复");
}
});
apis.forEach((apiName: string) => {
if (this.customApis.find(info => info === apiName)) {
throw new Error(pluginName + "插件中" + apiName + "重复");
}
});
return true;
}
// 绑定hooks方法
private _bindingHooks(plugin: IPluginTempl) {
this.hooks.forEach(hookName => {
const hook = plugin[hookName];
if (hook) {
this.hooksEntity[hookName].tapPromise(
plugin.pluginName + hookName,
function () {
// eslint-disable-next-line prefer-rest-params
return hook.apply(plugin, [...arguments]);
}
);
}
});
}
// 绑定快捷键
private _bindingHotkeys(plugin: IPluginTempl) {
plugin?.hotkeys?.forEach((keyName: string) => {
// 支持 keyup
hotkeys(keyName, { keyup: true }, e => plugin.hotkeyEvent(keyName, e));
});
}
// 保存组件自定义事件与API
private _saveCustomAttr(plugin: IPluginClass) {
const { events = [], apis = [] } = plugin;
this.customApis = this.customApis.concat(apis);
this.customEvents = this.customEvents.concat(events);
}
// 代理API事件
private _bindingApis(pluginRunTime: IPluginTempl) {
const { apis = [] } = pluginRunTime.constructor;
apis.forEach(apiName => {
this[apiName] = function () {
// eslint-disable-next-line prefer-rest-params
return pluginRunTime[apiName].apply(pluginRunTime, [...arguments]);
};
});
}
// 右键菜单
private _bindContextMenu() {
this.canvas.on("mouse:down", opt => {
if (opt.button === 3) {
let menu: IPluginMenu[] = [];
Object.keys(this.pluginMap).forEach(pluginName => {
const pluginRunTime = this.pluginMap[pluginName];
const pluginMenu =
pluginRunTime.contextMenu && pluginRunTime.contextMenu();
if (pluginMenu) {
menu = menu.concat(pluginMenu);
}
});
this._renderMenu(opt, menu);
}
});
}
// 渲染右键菜单
private _renderMenu(opt: fabric.IEvent, menu: IPluginMenu[]) {
if (menu.length !== 0) {
this.contextMenu.hideAll();
this.contextMenu.setData(menu);
this.contextMenu.show(opt.e.clientX, opt.e.clientY);
}
}
// 生命周期事件
_initActionHooks() {
this.hooks.forEach(hookName => {
this.hooksEntity[hookName] = new AsyncSeriesHook(["data"]);
});
}
_initContextMenu() {
this.contextMenu = new ContextMenu(this.canvas.wrapperEl, []);
this.contextMenu.install();
}
_initServersPlugin() {
this.use(ServersPlugin, {});
}
}
export default Editor;

@ -0,0 +1,50 @@
/*
* @Author:
* @Date: 2023-02-03 23:29:34
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:13:16
* @Description:
*/
import Editor from "./core";
import DringPlugin from "./plugin/DringPlugin";
import AlignGuidLinePlugin from "./plugin/AlignGuidLinePlugin";
import ControlsPlugin from "./plugin/ControlsPlugin";
import ControlsRotatePlugin from "./plugin/ControlsRotatePlugin";
import CenterAlignPlugin from "./plugin/CenterAlignPlugin";
import LayerPlugin from "./plugin/LayerPlugin";
import CopyPlugin from "./plugin/CopyPlugin";
import MoveHotKeyPlugin from "./plugin/MoveHotKeyPlugin";
import DeleteHotKeyPlugin from "./plugin/DeleteHotKeyPlugin";
import GroupPlugin from "./plugin/GroupPlugin";
import DrawLinePlugin from "./plugin/DrawLinePlugin";
import GroupTextEditorPlugin from "./plugin/GroupTextEditorPlugin";
import GroupAlignPlugin from "./plugin/GroupAlignPlugin";
import WorkspacePlugin from "./plugin/WorkspacePlugin";
import DownFontPlugin from "./plugin/DownFontPlugin";
import HistoryPlugin from "./plugin/HistoryPlugin";
import FlipPlugin from "./plugin/FlipPlugin";
import RulerPlugin from "./plugin/RulerPlugin";
import MaterialPlugin from "./plugin/MaterialPlugin";
export {
DringPlugin,
AlignGuidLinePlugin,
ControlsPlugin,
ControlsRotatePlugin,
CenterAlignPlugin,
LayerPlugin,
CopyPlugin,
MoveHotKeyPlugin,
DeleteHotKeyPlugin,
GroupPlugin,
DrawLinePlugin,
GroupTextEditorPlugin,
GroupAlignPlugin,
WorkspacePlugin,
DownFontPlugin,
HistoryPlugin,
FlipPlugin,
RulerPlugin,
MaterialPlugin
};
export default Editor;

@ -0,0 +1,46 @@
/*
* @Author:
* @Date: 2023-01-07 01:15:50
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:12:08
* @Description:
*/
import { fabric } from "fabric";
fabric.Arrow = fabric.util.createClass(fabric.Line, {
type: "arrow",
superType: "drawing",
initialize(points, options) {
if (!points) {
const { x1, x2, y1, y2 } = options;
points = [x1, y1, x2, y2];
}
options = options || {};
this.callSuper("initialize", points, options);
},
_render(ctx) {
this.callSuper("_render", ctx);
ctx.save();
const xDiff = this.x2 - this.x1;
const yDiff = this.y2 - this.y1;
const angle = Math.atan2(yDiff, xDiff);
ctx.translate((this.x2 - this.x1) / 2, (this.y2 - this.y1) / 2);
ctx.rotate(angle);
ctx.beginPath();
// Move 5px in front of line to start the arrow so it does not have the square line end showing in front (0,0)
ctx.moveTo(5, 0);
ctx.lineTo(-5, 5);
ctx.lineTo(-5, -5);
ctx.closePath();
ctx.fillStyle = this.stroke;
ctx.fill();
ctx.restore();
}
});
fabric.Arrow.fromObject = (options, callback) => {
const { x1, x2, y1, y2 } = options;
return callback(new fabric.Arrow([x1, y1, x2, y2], options));
};
export default fabric.Arrow;

@ -0,0 +1,101 @@
/*
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-08-06 17:11:20
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:13:06
* @FilePath: \General-AI-Platform-Web-Client\src\core\plugin.ts
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import Editor from "./core";
type IEditor = Editor;
class EditorWorkspacePlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
public defautOption = {
color: "red",
size: 0.5
};
static pluginName = "textPlugin";
static events = ["textEvent1", "textEvent2"];
static apis = ["textAPI1", "textAPI2"];
public hotkeys: string[] = ["ctrl+v", "ctrl+a"];
constructor(
canvas: fabric.Canvas,
editor: IEditor,
options: IPluginOption = {}
) {
this.canvas = canvas;
this.editor = editor;
this.defautOption = { ...this.defautOption, ...options };
this.init();
}
init() {
console.log("pluginInit", this.canvas, this.editor, this.defautOption);
}
destroy() {
console.log("pluginDestroy");
}
// 保存文件前
hookSaveBefore() {
console.log("pluginHookSaveBefore");
}
// 保存文件前
hookSaveAfter() {
console.log("pluginHookSaveAfter");
}
// 快捷键扩展回调
hotkeyEvent(eventName: string, e?: Event) {
console.log("pluginHotkeyEvent", eventName, e);
}
// 右键菜单扩展
contextMenu() {
return [
{ text: "Back", hotkey: "Alt+Left arrow", disabled: true },
{ text: "Forward", hotkey: "Alt+Right arrow", disabled: true },
{ text: "Reload", hotkey: "Ctrl+R" },
null,
{ text: "Save as...", hotkey: "Ctrl+S" },
{ text: "Print...", hotkey: "Ctrl+P" },
{ text: "Cast..." },
{ text: "Translate to English" },
null,
{ text: "View page source", hotkey: "Ctrl+U" },
{ text: "Inspect", hotkey: "Ctrl+Shift+I" },
null,
{
text: "Kali tools",
hotkey: "",
subitems: [
{
text: "Fuzzing Tools",
hotkey: "",
subitems: [
{ text: "spike-generic_chunked" },
{ text: "spike-generic_listen_tcp" },
{ text: "spike-generic_send_tcp" },
{ text: "spike-generic_send_udp" }
]
},
{
text: "VoIP Tools",
hotkey: "",
subitems: [{ text: "voiphopper" }]
},
{ text: "nikto" },
{ text: "nmap" },
{ text: "sparta" },
{ text: "unix-privesc-check" }
]
},
{ text: "Skins", hotkey: "" }
];
}
_command() {
console.log("pluginContextMenuCommand");
}
}
export default EditorWorkspacePlugin;

@ -0,0 +1,357 @@
/*
* @Author:
* @Date: 2023-05-21 08:55:25
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:13:57
* @Description: 线
*/
import Editor from "../core";
type IEditor = Editor;
import { fabric } from "fabric";
declare interface VerticalLine {
x: number;
y1: number;
y2: number;
}
declare interface HorizontalLine {
x1: number;
x2: number;
y: number;
}
class AlignGuidLinePlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
public defautOption = {
color: "rgba(255,95,95,1)",
width: 1
};
static pluginName = "AlignGuidLinePlugin";
static events = ["", ""];
static apis = [];
public hotkeys: string[] = [""];
dragMode = false;
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
this.dragMode = false;
this.init();
}
init() {
const { canvas } = this;
const ctx = canvas.getSelectionContext();
const aligningLineOffset = 5;
const aligningLineMargin = 4;
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this;
let viewportTransform: number[] | undefined;
let zoom = 1;
function drawVerticalLine(coords: VerticalLine) {
drawLine(
coords.x + 0.5,
coords.y1 > coords.y2 ? coords.y2 : coords.y1,
coords.x + 0.5,
coords.y2 > coords.y1 ? coords.y2 : coords.y1
);
}
function drawHorizontalLine(coords: HorizontalLine) {
drawLine(
coords.x1 > coords.x2 ? coords.x2 : coords.x1,
coords.y + 0.5,
coords.x2 > coords.x1 ? coords.x2 : coords.x1,
coords.y + 0.5
);
}
function drawLine(x1: number, y1: number, x2: number, y2: number) {
if (viewportTransform == null) return;
ctx.save();
ctx.lineWidth = self.defautOption.width;
ctx.strokeStyle = self.defautOption.color;
ctx.beginPath();
ctx.moveTo(
x1 * zoom + viewportTransform[4],
y1 * zoom + viewportTransform[5]
);
ctx.lineTo(
x2 * zoom + viewportTransform[4],
y2 * zoom + viewportTransform[5]
);
ctx.stroke();
ctx.restore();
}
function isInRange(value1: number, value2: number) {
value1 = Math.round(value1);
value2 = Math.round(value2);
for (
let i = value1 - aligningLineMargin, len = value1 + aligningLineMargin;
i <= len;
i++
) {
if (i === value2) {
return true;
}
}
return false;
}
const verticalLines: VerticalLine[] = [];
const horizontalLines: HorizontalLine[] = [];
canvas.on("mouse:down", () => {
viewportTransform = canvas.viewportTransform;
zoom = canvas.getZoom();
});
canvas.on("object:moving", e => {
if (viewportTransform === undefined || e.target === undefined) return;
const activeObject = e.target;
const canvasObjects = canvas.getObjects();
const activeObjectCenter = activeObject.getCenterPoint();
const activeObjectLeft = activeObjectCenter.x;
const activeObjectTop = activeObjectCenter.y;
const activeObjectBoundingRect = activeObject.getBoundingRect();
const activeObjectHeight =
activeObjectBoundingRect.height / viewportTransform[3];
const activeObjectWidth =
activeObjectBoundingRect.width / viewportTransform[0];
let horizontalInTheRange = false;
let verticalInTheRange = false;
const transform = canvas._currentTransform;
if (!transform) return;
// It should be trivial to DRY this up by encapsulating (repeating) creation of x1, x2, y1, and y2 into functions,
// but we're not doing it here for perf. reasons -- as this a function that's invoked on every mouse move
for (let i = canvasObjects.length; i--; ) {
// eslint-disable-next-line no-continue
if (canvasObjects[i] === activeObject) continue;
// 排除辅助线
if (
activeObject instanceof fabric.GuideLine &&
canvasObjects[i] instanceof fabric.GuideLine
) {
continue;
}
const objectCenter = canvasObjects[i].getCenterPoint();
const objectLeft = objectCenter.x;
const objectTop = objectCenter.y;
const objectBoundingRect = canvasObjects[i].getBoundingRect();
const objectHeight = objectBoundingRect.height / viewportTransform[3];
const objectWidth = objectBoundingRect.width / viewportTransform[0];
// snap by the horizontal center line
if (isInRange(objectLeft, activeObjectLeft)) {
verticalInTheRange = true;
verticalLines.push({
x: objectLeft,
y1:
objectTop < activeObjectTop
? objectTop - objectHeight / 2 - aligningLineOffset
: objectTop + objectHeight / 2 + aligningLineOffset,
y2:
activeObjectTop > objectTop
? activeObjectTop + activeObjectHeight / 2 + aligningLineOffset
: activeObjectTop - activeObjectHeight / 2 - aligningLineOffset
});
activeObject.setPositionByOrigin(
new fabric.Point(objectLeft, activeObjectTop),
"center",
"center"
);
}
// snap by the left edge
if (
isInRange(
objectLeft - objectWidth / 2,
activeObjectLeft - activeObjectWidth / 2
)
) {
verticalInTheRange = true;
verticalLines.push({
x: objectLeft - objectWidth / 2,
y1:
objectTop < activeObjectTop
? objectTop - objectHeight / 2 - aligningLineOffset
: objectTop + objectHeight / 2 + aligningLineOffset,
y2:
activeObjectTop > objectTop
? activeObjectTop + activeObjectHeight / 2 + aligningLineOffset
: activeObjectTop - activeObjectHeight / 2 - aligningLineOffset
});
activeObject.setPositionByOrigin(
new fabric.Point(
objectLeft - objectWidth / 2 + activeObjectWidth / 2,
activeObjectTop
),
"center",
"center"
);
}
// snap by the right edge
if (
isInRange(
objectLeft + objectWidth / 2,
activeObjectLeft + activeObjectWidth / 2
)
) {
verticalInTheRange = true;
verticalLines.push({
x: objectLeft + objectWidth / 2,
y1:
objectTop < activeObjectTop
? objectTop - objectHeight / 2 - aligningLineOffset
: objectTop + objectHeight / 2 + aligningLineOffset,
y2:
activeObjectTop > objectTop
? activeObjectTop + activeObjectHeight / 2 + aligningLineOffset
: activeObjectTop - activeObjectHeight / 2 - aligningLineOffset
});
activeObject.setPositionByOrigin(
new fabric.Point(
objectLeft + objectWidth / 2 - activeObjectWidth / 2,
activeObjectTop
),
"center",
"center"
);
}
// snap by the vertical center line
if (isInRange(objectTop, activeObjectTop)) {
horizontalInTheRange = true;
horizontalLines.push({
y: objectTop,
x1:
objectLeft < activeObjectLeft
? objectLeft - objectWidth / 2 - aligningLineOffset
: objectLeft + objectWidth / 2 + aligningLineOffset,
x2:
activeObjectLeft > objectLeft
? activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset
: activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset
});
activeObject.setPositionByOrigin(
new fabric.Point(activeObjectLeft, objectTop),
"center",
"center"
);
}
// snap by the top edge
if (
isInRange(
objectTop - objectHeight / 2,
activeObjectTop - activeObjectHeight / 2
)
) {
horizontalInTheRange = true;
horizontalLines.push({
y: objectTop - objectHeight / 2,
x1:
objectLeft < activeObjectLeft
? objectLeft - objectWidth / 2 - aligningLineOffset
: objectLeft + objectWidth / 2 + aligningLineOffset,
x2:
activeObjectLeft > objectLeft
? activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset
: activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset
});
activeObject.setPositionByOrigin(
new fabric.Point(
activeObjectLeft,
objectTop - objectHeight / 2 + activeObjectHeight / 2
),
"center",
"center"
);
}
// snap by the bottom edge
if (
isInRange(
objectTop + objectHeight / 2,
activeObjectTop + activeObjectHeight / 2
)
) {
horizontalInTheRange = true;
horizontalLines.push({
y: objectTop + objectHeight / 2,
x1:
objectLeft < activeObjectLeft
? objectLeft - objectWidth / 2 - aligningLineOffset
: objectLeft + objectWidth / 2 + aligningLineOffset,
x2:
activeObjectLeft > objectLeft
? activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset
: activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset
});
activeObject.setPositionByOrigin(
new fabric.Point(
activeObjectLeft,
objectTop + objectHeight / 2 - activeObjectHeight / 2
),
"center",
"center"
);
}
}
if (!horizontalInTheRange) {
horizontalLines.length = 0;
}
if (!verticalInTheRange) {
verticalLines.length = 0;
}
});
canvas.on("before:render", () => {
// fix 保存图片时报错
try {
canvas.clearContext(canvas.contextTop);
} catch (error) {
console.log(error);
}
});
canvas.on("after:render", () => {
for (let i = verticalLines.length; i--; ) {
drawVerticalLine(verticalLines[i]);
}
for (let j = horizontalLines.length; j--; ) {
drawHorizontalLine(horizontalLines[j]);
}
// noinspection NestedAssignmentJS
verticalLines.length = 0;
horizontalLines.length = 0;
});
canvas.on("mouse:up", () => {
verticalLines.length = 0;
horizontalLines.length = 0;
canvas.renderAll();
});
}
destroy() {
console.log("pluginDestroy");
}
}
export default AlignGuidLinePlugin;

@ -0,0 +1,76 @@
/*
* @Author:
* @Date: 2023-06-15 22:49:42
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:14:20
* @Description:
*/
import { fabric } from "fabric";
import Editor from "../core";
type IEditor = Editor;
class CenterAlignPlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
static pluginName = "CenterAlignPlugin";
static apis = ["centerH", "center", "position", "centerV"];
// public hotkeys: string[] = ['space'];
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
}
center(workspace: fabric.Rect, object: fabric.Object) {
const center = workspace.getCenterPoint();
return this.canvas._centerObject(object, center);
}
centerV(workspace: fabric.Rect, object: fabric.Object) {
return this.canvas._centerObject(
object,
new fabric.Point(object.getCenterPoint().x, workspace.getCenterPoint().y)
);
}
centerH(workspace: fabric.Rect, object: fabric.Object) {
return this.canvas._centerObject(
object,
new fabric.Point(workspace.getCenterPoint().x, object.getCenterPoint().y)
);
}
position(name: "centerH" | "center" | "centerV") {
const anignType = ["centerH", "center", "centerV"];
const activeObject = this.canvas.getActiveObject();
if (anignType.includes(name) && activeObject) {
const defaultWorkspace = this.canvas
.getObjects()
.find(item => item.id === "workspace");
if (defaultWorkspace) {
console.log(this[name]);
this[name](defaultWorkspace, activeObject);
}
this.canvas.renderAll();
}
}
contextMenu() {
const activeObject = this.canvas.getActiveObject();
if (activeObject) {
return [
{
text: "水平垂直居中",
hotkey: "Ctrl+V",
disabled: false,
onclick: () => this.position("center")
}
];
}
}
destroy() {
console.log("pluginDestroy");
}
}
export default CenterAlignPlugin;

@ -0,0 +1,262 @@
/*
* @Author:
* @Date: 2023-06-13 23:00:43
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-07 17:17:54
* @Description:
*/
import Editor from "../core";
type IEditor = Editor;
import { fabric } from "fabric";
import verticalImg from "@/assets/editor/middlecontrol.svg";
import horizontalImg from "@/assets/editor/middlecontrolhoz.svg";
import edgeImg from "@/assets/editor/edgecontrol.svg";
import rotateImg from "@/assets/editor/rotateicon.svg";
/**
* : fabricjs使toFixed(2)
* NUM_FRACTION_DIGITS4toFixed(4).
*/
fabric.Object.NUM_FRACTION_DIGITS = 4;
function drawImg(
ctx: CanvasRenderingContext2D,
left: number,
top: number,
img: HTMLImageElement,
wSize: number,
hSize: number,
angle: number | undefined
) {
if (angle === undefined) return;
ctx.save();
ctx.translate(left, top);
ctx.rotate(fabric.util.degreesToRadians(angle));
console.log(img, -wSize / 2, -hSize / 2, wSize, hSize, "drawImg");
ctx.drawImage(img, -wSize / 2, -hSize / 2, wSize, hSize);
ctx.restore();
}
// 中间横杠
function intervalControl() {
const verticalImgIcon = document.createElement("img");
verticalImgIcon.src = verticalImg;
const horizontalImgIcon = document.createElement("img");
horizontalImgIcon.src = horizontalImg;
function renderIcon(
ctx: CanvasRenderingContext2D,
left: number,
top: number,
styleOverride: any,
fabricObject: fabric.Object
) {
drawImg(ctx, left, top, verticalImgIcon, 20, 25, fabricObject.angle);
}
function renderIconHoz(
ctx: CanvasRenderingContext2D,
left: number,
top: number,
styleOverride: any,
fabricObject: fabric.Object
) {
drawImg(ctx, left, top, horizontalImgIcon, 25, 20, fabricObject.angle);
}
// 中间横杠
fabric.Object.prototype.controls.ml = new fabric.Control({
x: -0.5,
y: 0,
offsetX: -1,
cursorStyleHandler: fabric.controlsUtils.scaleSkewCursorStyleHandler,
actionHandler: fabric.controlsUtils.scalingXOrSkewingY,
getActionName: fabric.controlsUtils.scaleOrSkewActionName,
render: renderIcon
});
fabric.Object.prototype.controls.mr = new fabric.Control({
x: 0.5,
y: 0,
offsetX: 1,
cursorStyleHandler: fabric.controlsUtils.scaleSkewCursorStyleHandler,
actionHandler: fabric.controlsUtils.scalingXOrSkewingY,
getActionName: fabric.controlsUtils.scaleOrSkewActionName,
render: renderIcon
});
fabric.Object.prototype.controls.mb = new fabric.Control({
x: 0,
y: 0.5,
offsetY: 1,
cursorStyleHandler: fabric.controlsUtils.scaleSkewCursorStyleHandler,
actionHandler: fabric.controlsUtils.scalingYOrSkewingX,
getActionName: fabric.controlsUtils.scaleOrSkewActionName,
render: renderIconHoz
});
fabric.Object.prototype.controls.mt = new fabric.Control({
x: 0,
y: -0.5,
offsetY: -1,
cursorStyleHandler: fabric.controlsUtils.scaleSkewCursorStyleHandler,
actionHandler: fabric.controlsUtils.scalingYOrSkewingX,
getActionName: fabric.controlsUtils.scaleOrSkewActionName,
render: renderIconHoz
});
}
// 顶点
function peakControl() {
const img = document.createElement("img");
img.src = edgeImg;
function renderIconEdge(
ctx: CanvasRenderingContext2D,
left: number,
top: number,
styleOverride: any,
fabricObject: fabric.Object
) {
drawImg(ctx, left, top, img, 25, 25, fabricObject.angle);
}
// 四角图标
fabric.Object.prototype.controls.tl = new fabric.Control({
x: -0.5,
y: -0.5,
cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler,
actionHandler: fabric.controlsUtils.scalingEqually,
render: renderIconEdge
});
fabric.Object.prototype.controls.bl = new fabric.Control({
x: -0.5,
y: 0.5,
cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler,
actionHandler: fabric.controlsUtils.scalingEqually,
render: renderIconEdge
});
fabric.Object.prototype.controls.tr = new fabric.Control({
x: 0.5,
y: -0.5,
cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler,
actionHandler: fabric.controlsUtils.scalingEqually,
render: renderIconEdge
});
fabric.Object.prototype.controls.br = new fabric.Control({
x: 0.5,
y: 0.5,
cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler,
actionHandler: fabric.controlsUtils.scalingEqually,
render: renderIconEdge
});
}
// 删除
function deleteControl(canvas: fabric.Canvas) {
const deleteIcon =
"data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg version='1.1' id='Ebene_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='595.275px' height='595.275px' viewBox='200 215 230 470' xml:space='preserve'%3E%3Ccircle style='fill:%23F44336;' cx='299.76' cy='439.067' r='218.516'/%3E%3Cg%3E%3Crect x='267.162' y='307.978' transform='matrix(0.7071 -0.7071 0.7071 0.7071 -222.6202 340.6915)' style='fill:white;' width='65.545' height='262.18'/%3E%3Crect x='266.988' y='308.153' transform='matrix(0.7071 0.7071 -0.7071 0.7071 398.3889 -83.3116)' style='fill:white;' width='65.544' height='262.179'/%3E%3C/g%3E%3C/svg%3E";
const delImg = document.createElement("img");
delImg.src = deleteIcon;
function renderDelIcon(
ctx: CanvasRenderingContext2D,
left: number,
top: number,
styleOverride: any,
fabricObject: fabric.Object
) {
drawImg(ctx, left, top, delImg, 24, 24, fabricObject.angle);
}
// 删除选中元素
function deleteObject(mouseEvent: MouseEvent, target: fabric.Transform) {
if (target.action === "rotate") return true;
const activeObject = canvas.getActiveObjects();
if (activeObject) {
activeObject.map(item => canvas.remove(item));
canvas.requestRenderAll();
canvas.discardActiveObject();
}
return true;
}
// 删除图标
fabric.Object.prototype.controls.deleteControl = new fabric.Control({
x: 0.5,
y: -0.5,
offsetY: -16,
offsetX: 16,
cursorStyle: "pointer",
mouseUpHandler: deleteObject,
render: renderDelIcon
// cornerSize: 24,
});
}
// 旋转
function rotationControl() {
const img = document.createElement("img");
img.src = rotateImg;
function renderIconRotate(
ctx: CanvasRenderingContext2D,
left: number,
top: number,
styleOverride: any,
fabricObject: fabric.Object
) {
drawImg(ctx, left, top, img, 40, 40, fabricObject.angle);
}
// 旋转图标
fabric.Object.prototype.controls.mtr = new fabric.Control({
x: 0,
y: 0.5,
cursorStyleHandler: fabric.controlsUtils.rotationStyleHandler,
actionHandler: fabric.controlsUtils.rotationWithSnapping,
offsetY: 30,
// withConnecton: false,
actionName: "rotate",
render: renderIconRotate
});
}
class ControlsPlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
static pluginName = "ControlsPlugin";
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
this.init();
}
init() {
// 删除图标
deleteControl(this.canvas);
// TODO svg引入存在问题暂时注释
// 顶点图标
// peakControl();
// 中间横杠图标
// intervalControl();
// 旋转图标
// rotationControl();
// 选中样式
fabric.Object.prototype.set({
transparentCorners: false,
borderColor: "#51B9F9",
cornerColor: "#FFF",
borderScaleFactor: 2.5,
cornerStyle: "circle",
cornerStrokeColor: "#0E98FC",
borderOpacityWhenMoving: 1
});
// textbox保持一致
// fabric.Textbox.prototype.controls = fabric.Object.prototype.controls;
}
destroy() {
console.log("pluginDestroy");
}
}
export default ControlsPlugin;

@ -0,0 +1,122 @@
/*
* @Author:
* @Date: 2023-06-13 23:07:04
* @LastEditors:
* @LastEditTime: 2023-06-13 23:10:52
* @Description:
*/
import Editor from "../core";
type IEditor = Editor;
// 定义旋转光标样式,根据转动角度设定光标旋转
function rotateIcon(angle: number) {
return `url("data:image/svg+xml,%3Csvg height='18' width='18' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg' style='color: black;'%3E%3Cg fill='none' transform='rotate(${angle} 16 16)'%3E%3Cpath d='M22.4484 0L32 9.57891L22.4484 19.1478V13.1032C17.6121 13.8563 13.7935 17.6618 13.0479 22.4914H19.2141L9.60201 32.01L0 22.4813H6.54912C7.36524 14.1073 14.0453 7.44023 22.4484 6.61688V0Z' fill='white'/%3E%3Cpath d='M24.0605 3.89587L29.7229 9.57896L24.0605 15.252V11.3562C17.0479 11.4365 11.3753 17.0895 11.3048 24.0879H15.3048L9.60201 29.7308L3.90932 24.0879H8.0806C8.14106 15.3223 15.2645 8.22345 24.0605 8.14313V3.89587Z' fill='black'/%3E%3C/g%3E%3C/svg%3E ") 12 12,crosshair`;
}
class ControlsRotatePlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
static pluginName = "ControlsRotatePlugin";
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
this.init();
}
init() {
const { canvas } = this;
// 添加旋转控制响应区域
fabric.Object.prototype.controls.mtr = new fabric.Control({
x: -0.5,
y: -0.5,
offsetY: -10,
offsetX: -10,
rotate: 20,
actionName: "rotate",
actionHandler: fabric.controlsUtils.rotationWithSnapping,
render: () => ""
});
// ↖左上
fabric.Object.prototype.controls.mtr2 = new fabric.Control({
x: 0.5,
y: -0.5,
offsetY: -10,
offsetX: 10,
rotate: 20,
actionName: "rotate",
actionHandler: fabric.controlsUtils.rotationWithSnapping,
render: () => ""
}); // ↗右上
fabric.Object.prototype.controls.mtr3 = new fabric.Control({
x: 0.5,
y: 0.5,
offsetY: 10,
offsetX: 10,
rotate: 20,
actionName: "rotate",
actionHandler: fabric.controlsUtils.rotationWithSnapping,
render: () => ""
}); // ↘右下
fabric.Object.prototype.controls.mtr4 = new fabric.Control({
x: -0.5,
y: 0.5,
offsetY: 10,
offsetX: -10,
rotate: 20,
actionName: "rotate",
actionHandler: fabric.controlsUtils.rotationWithSnapping,
render: () => ""
}); // ↙左下
// 渲染时,执行
canvas.on("after:render", () => {
const activeObj = canvas.getActiveObject();
const angle = activeObj?.angle?.toFixed(2);
if (angle !== undefined) {
fabric.Object.prototype.controls.mtr.cursorStyle = rotateIcon(
Number(angle)
);
fabric.Object.prototype.controls.mtr2.cursorStyle = rotateIcon(
Number(angle) + 90
);
fabric.Object.prototype.controls.mtr3.cursorStyle = rotateIcon(
Number(angle) + 180
);
fabric.Object.prototype.controls.mtr4.cursorStyle = rotateIcon(
Number(angle) + 270
);
}
});
// 旋转时,实时更新旋转控制图标
canvas.on("object:rotating", event => {
const body = canvas.lowerCanvasEl.nextSibling as HTMLElement;
const angle = canvas.getActiveObject()?.angle?.toFixed(2);
if (angle === undefined) return;
switch (event.transform?.corner) {
case "mtr":
body.style.cursor = rotateIcon(Number(angle));
break;
case "mtr2":
body.style.cursor = rotateIcon(Number(angle) + 90);
break;
case "mtr3":
body.style.cursor = rotateIcon(Number(angle) + 180);
break;
case "mtr4":
body.style.cursor = rotateIcon(Number(angle) + 270);
break;
default:
break;
} // 设置四角旋转光标
});
}
destroy() {
console.log("pluginDestroy");
}
}
export default ControlsRotatePlugin;
import { fabric } from "fabric";

@ -0,0 +1,122 @@
/*
* @Author:
* @Date: 2023-06-20 12:38:37
* @LastEditors:
* @LastEditTime: 2023-06-20 13:34:21
* @Description:
*/
import { fabric } from "fabric";
import Editor from "../core";
type IEditor = Editor;
import { v4 as uuid } from "uuid";
class CopyPlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
static pluginName = "CopyPlugin";
static apis = ["clone"];
public hotkeys: string[] = ["ctrl+v", "ctrl+c"];
private cache: null | fabric.ActiveSelection | fabric.Object;
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
this.cache = null;
}
// 多选对象复制
_copyActiveSelection(activeObject: fabric.Object) {
// 间距设置
const grid = 10;
const canvas = this.canvas;
activeObject?.clone((cloned: fabric.Object) => {
// 再次进行克隆,处理选择多个对象的情况
cloned.clone((clonedObj: fabric.ActiveSelection) => {
canvas.discardActiveObject();
if (clonedObj.left === undefined || clonedObj.top === undefined) return;
// 将克隆的画布重新赋值
clonedObj.canvas = canvas;
// 设置位置信息
clonedObj.set({
left: clonedObj.left + grid,
top: clonedObj.top + grid,
evented: true,
id: uuid()
});
clonedObj.forEachObject((obj: fabric.Object) => {
obj.id = uuid();
canvas.add(obj);
});
// 解决不可选择问题
clonedObj.setCoords();
canvas.setActiveObject(clonedObj);
canvas.requestRenderAll();
});
});
}
// 单个对象复制
_copyObject(activeObject: fabric.Object) {
// 间距设置
const grid = 10;
const canvas = this.canvas;
activeObject?.clone((cloned: fabric.Object) => {
if (cloned.left === undefined || cloned.top === undefined) return;
canvas.discardActiveObject();
// 设置位置信息
cloned.set({
left: cloned.left + grid,
top: cloned.top + grid,
evented: true,
id: uuid()
});
canvas.add(cloned);
canvas.setActiveObject(cloned);
canvas.requestRenderAll();
});
}
// 复制元素
clone(paramsActiveObeject: fabric.ActiveSelection | fabric.Object) {
const activeObject = paramsActiveObeject || this.canvas.getActiveObject();
if (!activeObject) return;
if (activeObject?.type === "activeSelection") {
this._copyActiveSelection(activeObject);
} else {
this._copyObject(activeObject);
}
}
// 快捷键扩展回调
hotkeyEvent(eventName: string, e: any) {
if (eventName === "ctrl+c" && e.type === "keydown") {
const activeObject = this.canvas.getActiveObject();
this.cache = activeObject;
}
if (eventName === "ctrl+v" && e.type === "keydown") {
if (this.cache) {
this.clone(this.cache);
}
}
}
contextMenu() {
const activeObject = this.canvas.getActiveObject();
if (activeObject) {
return [
{
text: "复制",
hotkey: "Ctrl+V",
disabled: false,
onclick: () => this.clone()
}
];
}
}
destroy() {
console.log("pluginDestroy");
}
}
export default CopyPlugin;

@ -0,0 +1,62 @@
/*
* @Author:
* @Date: 2023-06-20 12:57:35
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:14:38
* @Description:
*/
import { fabric } from "fabric";
import Editor from "../core";
type IEditor = Editor;
// import { v4 as uuid } from 'uuid';
class DeleteHotKeyPlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
static pluginName = "DeleteHotKeyPlugin";
static apis = ["del"];
public hotkeys: string[] = ["backspace"];
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
}
// 快捷键扩展回调
hotkeyEvent(eventName: string, e: any) {
if (e.type === "keydown" && eventName === "backspace") {
this.del();
}
}
del() {
const { canvas } = this;
const activeObject = canvas.getActiveObjects();
if (activeObject) {
activeObject.map(item => canvas.remove(item));
canvas.requestRenderAll();
canvas.discardActiveObject();
}
}
contextMenu() {
const activeObject = this.canvas.getActiveObject();
if (activeObject) {
return [
null,
{
text: "删除",
hotkey: "Ctrl+V",
disabled: false,
onclick: () => this.del()
}
];
}
}
destroy() {
console.log("pluginDestroy");
}
}
export default DeleteHotKeyPlugin;

@ -0,0 +1,33 @@
/*
* @Author:
* @Date: 2023-06-27 22:58:57
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:14:43
* @Description:
*/
import { downFontByJSON } from "@/utils/utils";
import { fabric } from "fabric";
import Editor from "../core";
type IEditor = Editor;
class DownFontPlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
static pluginName = "DownFontPlugin";
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
}
hookImportBefore(json) {
// console.log(downFontByJSON(json).then, 111);
return downFontByJSON(json);
}
destroy() {
console.log("pluginDestroy");
}
}
export default DownFontPlugin;

@ -0,0 +1,138 @@
/*
* @Author:
* @Date: 2023-06-21 22:09:36
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:14:50
* @Description: file content
*/
import { v4 as uuid } from "uuid";
import { fabric } from "fabric";
import Arrow from "@/core/objects/Arrow";
import Editor from "../core";
type IEditor = Editor;
class DrawLinePlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
static pluginName = "DrawLinePlugin";
static apis = ["setArrow", "setMode"];
isDrawingLineMode: boolean;
isArrow: boolean;
lineToDraw: any;
pointer: any;
pointerPoints: any;
isDrawingLine: boolean;
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
this.isDrawingLine = false;
this.isDrawingLineMode = false;
this.isArrow = false;
this.lineToDraw = null;
this.pointer = null;
this.pointerPoints = null;
this.init();
}
init() {
const { canvas } = this;
canvas.on("mouse:down", o => {
if (!this.isDrawingLineMode) return;
canvas.discardActiveObject();
canvas.getObjects().forEach(obj => {
obj.selectable = false;
obj.hasControls = false;
});
canvas.requestRenderAll();
this.isDrawingLine = true;
this.pointer = canvas.getPointer(o.e);
this.pointerPoints = [
this.pointer.x,
this.pointer.y,
this.pointer.x,
this.pointer.y
];
const NodeHandler = this.isArrow ? Arrow : fabric.Line;
this.lineToDraw = new NodeHandler(this.pointerPoints, {
strokeWidth: 2,
stroke: "#000000",
id: uuid()
});
this.lineToDraw.selectable = false;
this.lineToDraw.evented = false;
this.lineToDraw.strokeUniform = true;
canvas.add(this.lineToDraw);
});
canvas.on("mouse:move", o => {
if (!this.isDrawingLine) return;
canvas.discardActiveObject();
const activeObject = canvas.getActiveObject();
if (activeObject) return;
this.pointer = canvas.getPointer(o.e);
if (o.e.shiftKey) {
// calc angle
const startX = this.pointerPoints[0];
const startY = this.pointerPoints[1];
const x2 = this.pointer.x - startX;
const y2 = this.pointer.y - startY;
const r = Math.sqrt(x2 * x2 + y2 * y2);
let angle = (Math.atan2(y2, x2) / Math.PI) * 180;
angle = parseInt(((angle + 7.5) % 360) / 15) * 15;
const cosx = r * Math.cos((angle * Math.PI) / 180);
const sinx = r * Math.sin((angle * Math.PI) / 180);
this.lineToDraw.set({
x2: cosx + startX,
y2: sinx + startY
});
} else {
this.lineToDraw.set({
x2: this.pointer.x,
y2: this.pointer.y
});
}
canvas.renderAll();
});
canvas.on("mouse:up", () => {
if (!this.isDrawingLine) return;
this.lineToDraw.setCoords();
this.isDrawingLine = false;
canvas.discardActiveObject();
});
}
setArrow(params: any) {
this.isArrow = params;
}
setMode(params: any) {
this.isDrawingLineMode = params;
if (!this.isDrawingLineMode) {
this.endRest();
}
}
endRest() {
this.canvas.getObjects().forEach(obj => {
if (obj.id !== "workspace") {
obj.selectable = true;
obj.hasControls = true;
}
});
}
destroy() {
console.log("pluginDestroy");
}
}
export default DrawLinePlugin;

@ -0,0 +1,126 @@
/*
* @Author:
* @Date: 2023-05-19 08:31:34
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:14:55
* @Description:
*/
import Editor from "../core";
type IEditor = Editor;
declare type ExtCanvas = fabric.Canvas & {
isDragging: boolean;
lastPosX: number;
lastPosY: number;
};
class DringPlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
public defautOption = {};
static pluginName = "DringPlugin";
static events = ["startDring", "endDring"];
static apis = ["startDring", "endDring"];
public hotkeys: string[] = ["space"];
dragMode = false;
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
this.dragMode = false;
this.init();
}
init() {
this._initDring();
}
startDring() {
this.dragMode = true;
this.canvas.defaultCursor = "grab";
this.editor.emit("startDring");
this.canvas.renderAll();
}
endDring() {
this.dragMode = false;
this.canvas.defaultCursor = "default";
this.canvas.isDragging = false;
this.editor.emit("endDring");
this.canvas.renderAll();
}
// 拖拽模式;
_initDring() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this;
this.canvas.on("mouse:down", function (this: ExtCanvas, opt) {
const evt = opt.e;
if (evt.altKey || self.dragMode) {
self.canvas.defaultCursor = "grabbing";
self.canvas.discardActiveObject();
self._setDring();
this.selection = false;
this.isDragging = true;
this.lastPosX = evt.clientX;
this.lastPosY = evt.clientY;
this.requestRenderAll();
}
});
this.canvas.on("mouse:move", function (this: ExtCanvas, opt) {
if (this.isDragging) {
self.canvas.discardActiveObject();
self.canvas.defaultCursor = "grabbing";
const { e } = opt;
if (!this.viewportTransform) return;
const vpt = this.viewportTransform;
vpt[4] += e.clientX - this.lastPosX;
vpt[5] += e.clientY - this.lastPosY;
this.lastPosX = e.clientX;
this.lastPosY = e.clientY;
this.requestRenderAll();
}
});
this.canvas.on("mouse:up", function (this: ExtCanvas) {
if (!this.viewportTransform) return;
this.setViewportTransform(this.viewportTransform);
this.isDragging = false;
this.selection = true;
this.getObjects().forEach(obj => {
if (obj.id !== "workspace" && obj.hasControls) {
obj.selectable = true;
}
});
self.canvas.defaultCursor = "default";
this.requestRenderAll();
});
}
_setDring() {
this.canvas.selection = false;
this.canvas.defaultCursor = "grab";
this.canvas.getObjects().forEach(obj => {
obj.selectable = false;
});
this.canvas.requestRenderAll();
}
destroy() {
console.log("pluginDestroy");
}
// 快捷键扩展回调
// eslint-disable-next-line @typescript-eslint/no-explicit-any
hotkeyEvent(eventName: string, e: any) {
if (e.code === "Space" && e.type === "keydown") {
if (!this.dragMode) {
this.startDring();
}
}
if (e.code === "Space" && e.type === "keyup") {
this.endDring();
}
}
}
export default DringPlugin;

@ -0,0 +1,76 @@
/*
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-08-06 17:11:20
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:29:22
* @FilePath: \General-AI-Platform-Web-Client\src\core\plugin\FlipPlugin.ts
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { fabric } from "fabric";
import type Editor from "../core";
import { SelectEvent, SelectMode } from "@/utils/event/types";
import { Ref } from "vue";
import { $t } from "@/plugins/i18n";
import event from "@/utils/event/notifier";
export default class FlipPlugin {
public canvas: fabric.Canvas;
public editor: Editor;
static pluginName = "FlipPlugin";
static apis = ["flip"];
selectedMode: Ref<SelectMode>;
constructor(canvas: fabric.Canvas, editor: Editor) {
this.canvas = canvas;
this.editor = editor;
this.selectedMode = ref(SelectMode.EMPTY);
this.init();
}
init() {
event.on(SelectEvent.ONE, () => (this.selectedMode.value = SelectMode.ONE));
event.on(
SelectEvent.MULTI,
() => (this.selectedMode.value = SelectMode.MULTI)
);
event.on(
SelectEvent.CANCEL,
() => (this.selectedMode.value = SelectMode.EMPTY)
);
}
flip(type: "X" | "Y") {
const activeObject = this.canvas.getActiveObject();
if (activeObject) {
activeObject.set(`flip${type}`, !activeObject[`flip${type}`]).setCoords();
this.canvas.requestRenderAll();
}
}
contextMenu() {
if (this.selectedMode.value === SelectMode.ONE) {
return [
{
text: "翻转",
hotkey: "",
subitems: [
{
text: $t("flip.x"),
hotkey: "|",
onclick: () => this.flip("X")
},
{
text: $t("flip.y"),
hotkey: "-",
onclick: () => this.flip("Y")
}
]
}
];
}
}
destroy() {
console.log("pluginDestroy");
}
}

@ -0,0 +1,338 @@
/*
* @Author:
* @Date: 2023-06-22 16:19:46
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:29:36
* @Description:
*/
import { fabric } from "fabric";
import Editor from "../core";
type IEditor = Editor;
class GroupAlignPlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
static pluginName = "GroupAlignPlugin";
static apis = [
"left",
"right",
"xcenter",
"ycenter",
"top",
"bottom",
"xequation",
"yequation"
];
// public hotkeys: string[] = ['space'];
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
}
left() {
const { canvas } = this;
// const activeObject = canvas.getActiveObject();
// if (activeObject && activeObject.type === 'activeSelection') {
// const activeSelection = activeObject;
// const activeObjectLeft = -(activeObject.width / 2);
// activeSelection.forEachObject((item) => {
// item.set({
// left: activeObjectLeft,
// });
// item.setCoords();
// canvas.renderAll();
// });
// }
const activeObject = canvas.getActiveObject();
const selectObjects = canvas.getActiveObjects();
const { left } = activeObject;
canvas.discardActiveObject();
selectObjects.forEach(item => {
const bounding = item.getBoundingRect(true);
item.set({
left: left - bounding.left + item.left
});
item.setCoords();
});
const activeSelection = new fabric.ActiveSelection(selectObjects, {
canvas: canvas
});
canvas.setActiveObject(activeSelection);
canvas.requestRenderAll();
}
right() {
const { canvas } = this;
// const activeObject = canvas.getActiveObject();
// if (activeObject && activeObject.type === 'activeSelection') {
// const activeSelection = activeObject;
// const activeObjectLeft = activeObject.width / 2;
// activeSelection.forEachObject((item) => {
// item.set({
// left: activeObjectLeft - item.width * item.scaleX,
// });
// item.setCoords();
// canvas.renderAll();
// });
// }
const activeObject = canvas.getActiveObject();
const selectObjects = canvas.getActiveObjects();
const { left, width } = activeObject;
canvas.discardActiveObject();
selectObjects.forEach(item => {
const bounding = item.getBoundingRect(true);
item.set({
left: left + width - (bounding.left + bounding.width) + item.left
});
});
const activeSelection = new fabric.ActiveSelection(selectObjects, {
canvas: canvas
});
canvas.setActiveObject(activeSelection);
canvas.requestRenderAll();
}
xcenter() {
const { canvas } = this;
// const activeObject = canvas.getActiveObject();
// if (activeObject && activeObject.type === 'activeSelection') {
// const activeSelection = activeObject;
// activeSelection.forEachObject((item) => {
// item.set({
// left: 0 - (item.width * item.scaleX) / 2,
// });
// item.setCoords();
// canvas.renderAll();
// });
// }
const activeObject = canvas.getActiveObject();
const selectObjects = canvas.getActiveObjects();
const { left, width } = activeObject;
canvas.discardActiveObject();
selectObjects.forEach(item => {
const bounding = item.getBoundingRect(true);
item.set({
left:
left + width / 2 - (bounding.left + bounding.width / 2) + item.left
});
});
const activeSelection = new fabric.ActiveSelection(selectObjects, {
canvas: canvas
});
canvas.setActiveObject(activeSelection);
canvas.requestRenderAll();
}
ycenter() {
const { canvas } = this;
// const activeObject = canvas.getActiveObject();
// if (activeObject && activeObject.type === 'activeSelection') {
// const activeSelection = activeObject;
// activeSelection.forEachObject((item) => {
// item.set({
// top: 0 - (item.height * item.scaleY) / 2,
// });
// item.setCoords();
// canvas.renderAll();
// });
// }
const activeObject = canvas.getActiveObject();
const selectObjects = canvas.getActiveObjects();
const { top, height } = activeObject;
canvas.discardActiveObject();
selectObjects.forEach(item => {
const bounding = item.getBoundingRect(true);
item.set({
top: top + height / 2 - (bounding.top + bounding.height / 2) + item.top
});
});
const activeSelection = new fabric.ActiveSelection(selectObjects, {
canvas: canvas
});
canvas.setActiveObject(activeSelection);
canvas.requestRenderAll();
}
top() {
const { canvas } = this;
// const activeObject = canvas.getActiveObject();
// if (activeObject && activeObject.type === 'activeSelection') {
// const activeSelection = activeObject;
// const activeObjectTop = -(activeObject.height / 2);
// activeSelection.forEachObject((item) => {
// item.set({
// top: activeObjectTop,
// });
// item.setCoords();
// canvas.renderAll();
// });
// }
const activeObject = canvas.getActiveObject();
const selectObjects = canvas.getActiveObjects();
const { top } = activeObject;
canvas.discardActiveObject();
selectObjects.forEach(item => {
const bounding = item.getBoundingRect(true);
item.set({
top: top - bounding.top + item.top
});
});
const activeSelection = new fabric.ActiveSelection(selectObjects, {
canvas: canvas
});
canvas.setActiveObject(activeSelection);
canvas.requestRenderAll();
}
bottom() {
const { canvas } = this;
// const activeObject = canvas.getActiveObject();
// if (activeObject && activeObject.type === 'activeSelection') {
// const activeSelection = activeObject;
// const activeObjectTop = activeObject.height / 2;
// activeSelection.forEachObject((item) => {
// item.set({
// top: activeObjectTop - item.height * item.scaleY,
// });
// item.setCoords();
// canvas.renderAll();
// });
// }
const activeObject = canvas.getActiveObject();
const selectObjects = canvas.getActiveObjects();
const { top, height } = activeObject;
canvas.discardActiveObject();
selectObjects.forEach(item => {
const bounding = item.getBoundingRect(true);
item.set({
top: top + height - (bounding.top + bounding.height) + item.top
});
});
const activeSelection = new fabric.ActiveSelection(selectObjects, {
canvas: canvas
});
canvas.setActiveObject(activeSelection);
canvas.requestRenderAll();
}
xequation() {
const { canvas } = this;
const activeObject = canvas.getActiveObject();
// width属性不准确需要坐标换算
function getItemWidth(item) {
return item.aCoords.tr.x - item.aCoords.tl.x;
}
// 获取所有元素高度
function getAllItemHeight() {
let count = 0;
activeObject.forEachObject(item => {
count += getItemWidth(item);
});
return count;
}
// 获取平均间距
function spacWidth() {
const count = getAllItemHeight();
const allSpac = activeObject.width - count;
return allSpac / (activeObject._objects.length - 1);
}
// 获取当前元素之前所有元素的高度
function getItemLeft(i) {
if (i === 0) return 0;
let width = 0;
for (let index = 0; index < i; index++) {
width += getItemWidth(activeObject._objects[index]);
}
return width;
}
if (activeObject && activeObject.type === "activeSelection") {
const activeSelection = activeObject;
// 排序
activeSelection._objects.sort((a, b) => a.left - b.left);
// 平均间距计算
const itemSpac = spacWidth();
// 组原点高度
const yHeight = activeObject.width / 2;
activeObject.forEachObject((item, i) => {
// 获取当前元素之前所有元素的高度
const preHeight = getItemLeft(i);
// 顶部距离 间距 * 索引 + 之前元素高度 - 原点高度
const top = itemSpac * i + preHeight - yHeight;
item.set("left", top);
});
canvas.renderAll();
}
}
yequation() {
const { canvas } = this;
const activeObject = canvas.getActiveObject();
// width属性不准确需要坐标换算
function getItemHeight(item) {
return item.aCoords.bl.y - item.aCoords.tl.y;
}
// 获取所有元素高度
function getAllItemHeight() {
let count = 0;
activeObject.forEachObject(item => {
count += getItemHeight(item);
});
return count;
}
// 获取平均间距
function spacHeight() {
const count = getAllItemHeight();
const allSpac = activeObject.height - count;
return allSpac / (activeObject._objects.length - 1);
}
// 获取当前元素之前所有元素的高度
function getItemTop(i) {
if (i === 0) return 0;
let height = 0;
for (let index = 0; index < i; index++) {
height += getItemHeight(activeObject._objects[index]);
}
return height;
}
if (activeObject && activeObject.type === "activeSelection") {
const activeSelection = activeObject;
// 排序
activeSelection._objects.sort((a, b) => a.top - b.top);
// 平均间距计算
const itemSpac = spacHeight();
// 组原点高度
const yHeight = activeObject.height / 2;
activeObject.forEachObject((item, i) => {
// 获取当前元素之前所有元素的高度
const preHeight = getItemTop(i);
// 顶部距离 间距 * 索引 + 之前元素高度 - 原点高度
const top = itemSpac * i + preHeight - yHeight;
item.set("top", top);
});
canvas.renderAll();
}
}
destroy() {
console.log("pluginDestroy");
}
}
export default GroupAlignPlugin;

@ -0,0 +1,83 @@
/*
* @Author:
* @Date: 2023-06-20 13:21:10
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:29:41
* @Description:
*/
import { fabric } from "fabric";
import Editor from "../core";
import { v4 as uuid } from "uuid";
type IEditor = Editor;
class GroupPlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
static pluginName = "GroupPlugin";
static apis = ["unGroup", "group"];
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
}
// 拆分组
unGroup() {
const activeObject = this.canvas.getActiveObject() as fabric.Group;
if (!activeObject) return;
// 先获取当前选中的对象,然后打散
activeObject.toActiveSelection();
activeObject.getObjects().forEach((item: fabric.Object) => {
item.set("id", uuid());
});
this.canvas.discardActiveObject().renderAll();
}
group() {
// 组合元素
const activeObj = this.canvas.getActiveObject() as fabric.ActiveSelection;
if (!activeObj) return;
const activegroup = activeObj.toGroup();
const objectsInGroup = activegroup.getObjects();
activegroup.clone((newgroup: fabric.Group) => {
newgroup.set("id", uuid());
this.canvas.remove(activegroup);
objectsInGroup.forEach(object => {
this.canvas.remove(object);
});
this.canvas.add(newgroup);
this.canvas.setActiveObject(newgroup);
});
}
contextMenu() {
const activeObject = this.canvas.getActiveObject();
console.log(activeObject, "111");
if (activeObject && activeObject.type === "group") {
return [
{
text: "拆分组合",
hotkey: "Ctrl+V",
disabled: false,
onclick: () => this.unGroup()
}
];
}
if (this.canvas.getActiveObjects().length > 1) {
return [
{
text: "组合",
hotkey: "Ctrl+V",
disabled: false,
onclick: () => this.group()
}
];
}
}
destroy() {
console.log("pluginDestroy");
}
}
export default GroupPlugin;

@ -0,0 +1,202 @@
/*
* @Author:
* @Date: 2023-06-22 16:11:40
* @LastEditors:
* @LastEditTime: 2023-08-07 23:24:36
* @Description:
*/
import { fabric } from "fabric";
import Editor from "../core";
import { v4 as uuid } from "uuid";
type IEditor = Editor;
class GroupTextEditorPlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
static pluginName = "GroupTextEditorPlugin";
isDown = false;
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
this._init();
}
// 组内文本输入
_init() {
this.canvas.on("mouse:down", opt => {
this.isDown = true;
// 重置选中controls
if (
opt.target &&
!opt.target.lockMovementX &&
!opt.target.lockMovementY &&
!opt.target.lockRotation &&
!opt.target.lockScalingX &&
!opt.target.lockScalingY
) {
opt.target.hasControls = true;
}
});
this.canvas.on("mouse:up", () => {
this.isDown = false;
});
this.canvas.on("mouse:dblclick", opt => {
if (opt.target && opt.target.type === "group") {
const selectedObject = this._getGroupObj(opt) as fabric.IText;
if (!selectedObject) return;
selectedObject.selectable = true;
// 由于组内的元素双击以后会导致controls偏移因此隐藏他
if (selectedObject.hasControls) {
selectedObject.hasControls = false;
}
if (this.isText(selectedObject)) {
this._bedingTextEditingEvent(selectedObject, opt);
return;
}
this.canvas.setActiveObject(selectedObject);
this.canvas.renderAll();
}
});
}
// 获取点击区域内的组内文字元素
_getGroupTextObj(opt: fabric.IEvent<MouseEvent>) {
const pointer = this.canvas.getPointer(opt.e, true);
const clickObj = this.canvas._searchPossibleTargets(
opt.target?._objects,
pointer
);
if (clickObj && this.isText(clickObj)) {
return clickObj;
}
return false;
}
_getGroupObj(opt: fabric.IEvent<MouseEvent>) {
const pointer = this.canvas.getPointer(opt.e, true);
const clickObj = this.canvas._searchPossibleTargets(
opt.target?._objects,
pointer
);
return clickObj;
}
// 通过组合重新组装来编辑文字,可能会耗性能。
_bedingTextEditingEvent(
textObject: fabric.IText,
opt: fabric.IEvent<MouseEvent>
) {
if (!opt.target) return;
const textObjectJSON = textObject.toObject();
const groupObj = opt.target;
const ftype: any = {
"i-text": "IText",
text: "Text",
textbox: "Textbox"
};
const eltype: string = ftype[textObjectJSON.type];
const groupMatrix: number[] = groupObj.calcTransformMatrix();
const a: number = groupMatrix[0];
const b: number = groupMatrix[1];
const c: number = groupMatrix[2];
const d: number = groupMatrix[3];
const e: number = groupMatrix[4];
const f: number = groupMatrix[5];
const newX = a * textObject.left + c * textObject.top + e;
const newY = b * textObject.left + d * textObject.top + f;
const tempText = new fabric[eltype](textObject.text, {
...textObjectJSON,
textAlign: textObject.textAlign,
left: newX,
top: newY,
styles: textObject.styles,
groupCopyed: textObject.group
});
tempText.id = uuid();
textObject.visible = false;
opt.target.addWithUpdate();
tempText.visible = true;
tempText.selectable = true;
tempText.hasConstrols = false;
tempText.editable = true;
this.canvas.add(tempText);
this.canvas.setActiveObject(tempText);
tempText.enterEditing();
tempText.selectAll();
tempText.on("editing:exited", () => {
// 进入编辑模式时触发
textObject.set({
text: tempText.text,
visible: true
});
opt.target.addWithUpdate();
tempText.visible = false;
this.canvas.remove(tempText);
this.canvas.setActiveObject(opt.target);
});
}
// 绑定编辑取消事件
_bedingEditingEvent(
textObject: fabric.IText,
opt: fabric.IEvent<MouseEvent>
) {
if (!opt.target) return;
const left = opt.target.left;
const top = opt.target.top;
const ids = this._unGroup() || [];
const resetGroup = () => {
const groupArr = this.canvas
.getObjects()
.filter(item => item.id && ids.includes(item.id));
// 删除元素
groupArr.forEach(item => this.canvas.remove(item));
// 生成新组
const group = new fabric.Group([...groupArr]);
group.set("left", left);
group.set("top", top);
group.set("id", uuid());
textObject.off("editing:exited", resetGroup);
this.canvas.add(group);
this.canvas.discardActiveObject().renderAll();
};
// 绑定取消事件
textObject.on("editing:exited", resetGroup);
}
// 拆分组合并返回ID
_unGroup() {
const ids: string[] = [];
const activeObj = this.canvas.getActiveObject() as fabric.Group;
if (!activeObj) return;
activeObj.getObjects().forEach(item => {
const id = uuid();
ids.push(id);
item.set("id", id);
});
activeObj.toActiveSelection();
return ids;
}
isText(obj: fabric.Object) {
return obj.type && ["i-text", "text", "textbox"].includes(obj.type);
}
destroy() {
console.log("pluginDestroy");
}
}
export default GroupTextEditorPlugin;

@ -0,0 +1,93 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/*
* @Author:
* @Date: 2023-06-20 13:06:31
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:29:49
* @Description:
*/
import { fabric } from "fabric";
import Editor from "../core";
import { ref } from "vue";
import { useRefHistory } from "@vueuse/core";
type IEditor = Editor;
// import { v4 as uuid } from 'uuid';
class HistoryPlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
static pluginName = "HistoryPlugin";
static apis = ["undo", "redo", "getHistory"];
static events = ["historyInitSuccess"];
public hotkeys: string[] = ["ctrl+z"];
history: any;
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
this._init();
}
_init() {
this.history = useRefHistory(ref(), {
capacity: 50
});
this.canvas.on({
"object:added": event => this._save(event),
"object:modified": event => this._save(event),
"selection:updated": event => this._save(event)
});
}
getHistory() {
return this.history;
}
_save(event) {
// 过滤选择元素事件
const isSelect = event.action === undefined && event.e;
if (isSelect || !this.canvas) return;
const workspace = this.canvas
.getObjects()
.find(item => item.id === "workspace");
if (!workspace) {
return;
}
if (this.history.isTracking.value) {
this.history.source.value = this.editor.getJson();
}
}
undo() {
if (this.history.canUndo.value) {
this.renderCanvas();
this.history.undo();
}
}
redo() {
this.history.redo();
this.renderCanvas();
}
renderCanvas = () => {
this.history.pause();
this.canvas.clear();
this.canvas.loadFromJSON(this.history.source.value, () => {
this.canvas.renderAll();
this.history.resume();
});
};
// 快捷键扩展回调
hotkeyEvent(eventName: string, e: any) {
if (eventName === "ctrl+z" && e.type === "keydown") {
this.undo();
}
}
destroy() {
console.log("pluginDestroy");
}
}
export default HistoryPlugin;

@ -0,0 +1,113 @@
/*
* @Author:
* @Date: 2023-06-15 23:23:18
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:29:53
* @Description:
*/
import { fabric } from "fabric";
import Editor from "../core";
type IEditor = Editor;
class LayerPlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
static pluginName = "LayerPlugin";
static apis = ["up", "upTop", "down", "downTop"];
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
}
_getWorkspace() {
return this.canvas.getObjects().find(item => item.id === "workspace");
}
_workspaceSendToBack() {
const workspace = this._getWorkspace();
workspace && workspace.sendToBack();
}
up() {
const actives = this.canvas.getActiveObjects();
if (actives && actives.length === 1) {
const activeObject = this.canvas.getActiveObjects()[0];
activeObject && activeObject.bringForward();
this.canvas.renderAll();
this._workspaceSendToBack();
}
}
upTop() {
const actives = this.canvas.getActiveObjects();
if (actives && actives.length === 1) {
const activeObject = this.canvas.getActiveObjects()[0];
activeObject && activeObject.bringToFront();
this.canvas.renderAll();
console.log(this);
this._workspaceSendToBack();
}
}
down() {
const actives = this.canvas.getActiveObjects();
if (actives && actives.length === 1) {
const activeObject = this.canvas.getActiveObjects()[0];
activeObject && activeObject.sendBackwards();
this.canvas.renderAll();
this._workspaceSendToBack();
}
}
downTop() {
const actives = this.canvas.getActiveObjects();
if (actives && actives.length === 1) {
const activeObject = this.canvas.getActiveObjects()[0];
activeObject && activeObject.sendToBack();
this.canvas.renderAll();
this._workspaceSendToBack();
}
}
contextMenu() {
const activeObject = this.canvas.getActiveObject();
if (activeObject) {
return [
{
text: "图层管理",
hotkey: "",
subitems: [
{
text: "上一个",
hotkey: "key",
onclick: () => this.up()
},
{
text: "下一个",
hotkey: "key",
onclick: () => this.down()
},
{
text: "置顶",
hotkey: "key",
onclick: () => this.upTop()
},
{
text: "置底",
hotkey: "key",
onclick: () => this.downTop()
}
]
}
];
// return [{ text: '复制', hotkey: 'Ctrl+V', disabled: false, onclick: () => this.clone() }];
}
}
destroy() {
console.log("pluginDestroy");
}
}
export default LayerPlugin;

@ -0,0 +1,45 @@
/*
* @Author:
* @Date: 2023-08-04 21:13:16
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:30:01
* @Description:
*/
import { fabric } from "fabric";
import Editor from "../core";
type IEditor = Editor;
import axios from "axios";
class MaterialPlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
static pluginName = "MaterialPlugin";
static apis = ["getMaterialType", "getMaterialList"];
apiMapUrl: { [propName: string]: string };
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
this.apiMapUrl = {
template:
"https://nihaojob.github.io/vue-fabric-editor-static/template/type.json",
svg: "https://nihaojob.github.io/vue-fabric-editor-static/svg/type.json"
};
}
// 根据素材类型获取分裂列表
async getMaterialType(typeId: string) {
const url = this.apiMapUrl[typeId];
const res = await axios.get(url, { params: { typeId } });
return res.data.data;
}
async getMaterialInfo(typeId: string) {
const url = this.apiMapUrl[typeId];
const res = await axios.get(url, { params: { typeId } });
return res.data.data;
}
}
export default MaterialPlugin;

@ -0,0 +1,58 @@
/*
* @Author:
* @Date: 2023-06-20 12:52:09
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:30:04
* @Description:
*/
import { fabric } from "fabric";
import Editor from "../core";
type IEditor = Editor;
// import { v4 as uuid } from 'uuid';
class MoveHotKeyPlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
static pluginName = "MoveHotKeyPlugin";
public hotkeys: string[] = ["left", "right", "down", "up"];
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
}
// 快捷键扩展回调
hotkeyEvent(eventName: string, e: any) {
if (e.type === "keydown") {
const { canvas } = this;
const activeObject = canvas.getActiveObject();
if (!activeObject) return;
switch (eventName) {
case "left":
if (activeObject.left === undefined) return;
activeObject.set("left", activeObject.left - 1);
break;
case "right":
if (activeObject.left === undefined) return;
activeObject.set("left", activeObject.left + 1);
break;
case "down":
if (activeObject.top === undefined) return;
activeObject.set("top", activeObject.top + 1);
break;
case "up":
if (activeObject.top === undefined) return;
activeObject.set("top", activeObject.top - 1);
break;
default:
}
canvas.renderAll();
}
}
destroy() {
console.log("pluginDestroy");
}
}
export default MoveHotKeyPlugin;

@ -0,0 +1,73 @@
/*
* @Author:
* @Date: 2023-07-04 23:45:49
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:30:08
* @Description:
*/
import { fabric } from "fabric";
import Editor from "../core";
// import { throttle } from 'lodash-es';
type IEditor = Editor;
import initRuler from "@/core/ruler";
class RulerPlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
static pluginName = "RulerPlugin";
// static events = ['sizeChange'];
static apis = [
"hideGuideline",
"showGuideline",
"rulerEnable",
"rulerDisable"
];
ruler: any;
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
this.init();
}
hookSaveBefore() {
return new Promise(resolve => {
this.hideGuideline();
resolve(true);
});
}
hookSaveAfter() {
return new Promise(resolve => {
this.showGuideline();
resolve(true);
});
}
init() {
this.ruler = initRuler(this.canvas);
}
hideGuideline() {
this.ruler.hideGuideline();
}
showGuideline() {
this.ruler.showGuideline();
}
rulerEnable() {
this.ruler.enable();
}
rulerDisable() {
this.ruler.disable();
}
destroy() {
console.log("pluginDestroy");
}
}
export default RulerPlugin;

@ -0,0 +1,232 @@
/*
* @Author:
* @Date: 2023-06-27 12:26:41
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:30:13
* @Description:
*/
import { fabric } from "fabric";
import Editor from "../core";
import { throttle } from "lodash-es";
type IEditor = Editor;
class WorkspacePlugin {
public canvas: fabric.Canvas;
public editor: IEditor;
static pluginName = "WorkspacePlugin";
static events = ["sizeChange"];
static apis = ["big", "small", "auto", "one", "setSize"];
workspaceEl: HTMLElement;
workspace: null | fabric.Rect;
option: any;
constructor(canvas: fabric.Canvas, editor: IEditor) {
this.canvas = canvas;
this.editor = editor;
this.init({
width: 900,
height: 2000
});
}
init(option) {
const workspaceEl = document.querySelector("#workspace") as HTMLElement;
if (!workspaceEl) {
throw new Error("element #workspace is missing, plz check!");
}
this.workspaceEl = workspaceEl;
this.workspace = null;
this.option = option;
this._initBackground();
this._initWorkspace();
this._initResizeObserve();
this._bindWheel();
}
// hookImportBefore() {
// return new Promise((resolve, reject) => {
// resolve();
// });
// }
hookImportAfter() {
return new Promise(resolve => {
const workspace = this.canvas
.getObjects()
.find(item => item.id === "workspace");
if (workspace) {
workspace.set("selectable", false);
workspace.set("hasControls", false);
this.setSize(workspace.width, workspace.height);
this.editor.emit("sizeChange", workspace.width, workspace.height);
}
resolve();
});
}
hookSaveAfter() {
return new Promise(resolve => {
this.auto();
resolve(true);
});
}
// 初始化背景
_initBackground() {
this.canvas.backgroundImage = "";
this.canvas.setWidth(this.workspaceEl.offsetWidth);
this.canvas.setHeight(this.workspaceEl.offsetHeight);
}
// 初始化画布
_initWorkspace() {
const { width, height } = this.option;
const workspace = new fabric.Rect({
fill: "rgba(255,255,255,1)",
width,
height,
id: "workspace",
strokeWidth: 0
});
workspace.set("selectable", false);
workspace.set("hasControls", false);
workspace.hoverCursor = "default";
this.canvas.add(workspace);
this.canvas.renderAll();
this.workspace = workspace;
this.auto();
}
/**
*
* @param {Object} obj
*/
setCenterFromObject(obj: fabric.Rect) {
const { canvas } = this;
const objCenter = obj.getCenterPoint();
const viewportTransform = canvas.viewportTransform;
if (
canvas.width === undefined ||
canvas.height === undefined ||
!viewportTransform
)
return;
viewportTransform[4] =
canvas.width / 2 - objCenter.x * viewportTransform[0];
viewportTransform[5] =
canvas.height / 2 - objCenter.y * viewportTransform[3];
canvas.setViewportTransform(viewportTransform);
canvas.renderAll();
}
// 初始化监听器
_initResizeObserve() {
const resizeObserver = new ResizeObserver(
throttle(() => {
this.auto();
}, 50)
);
resizeObserver.observe(this.workspaceEl);
}
setSize(width: number, height: number) {
this._initBackground();
this.option.width = width;
this.option.height = height;
// 重新设置workspace
this.workspace = this.canvas
.getObjects()
.find(item => item.id === "workspace") as fabric.Rect;
this.workspace.set("width", width);
this.workspace.set("height", height);
this.auto();
}
setZoomAuto(scale: number, cb?: (left?: number, top?: number) => void) {
const { workspaceEl } = this;
const width = workspaceEl.offsetWidth;
const height = workspaceEl.offsetHeight;
this.canvas.setWidth(width);
this.canvas.setHeight(height);
const center = this.canvas.getCenter();
this.canvas.setViewportTransform(fabric.iMatrix.concat());
this.canvas.zoomToPoint(new fabric.Point(center.left, center.top), scale);
if (!this.workspace) return;
this.setCenterFromObject(this.workspace);
// 超出画布不展示
this.workspace.clone((cloned: fabric.Rect) => {
this.canvas.clipPath = cloned;
this.canvas.requestRenderAll();
});
if (cb) cb(this.workspace.left, this.workspace.top);
}
_getScale() {
const viewPortWidth = this.workspaceEl.offsetWidth;
const viewPortHeight = this.workspaceEl.offsetHeight;
// 按照宽度
if (
viewPortWidth / viewPortHeight <
this.option.width / this.option.height
) {
return viewPortWidth / this.option.width;
} // 按照宽度缩放
return viewPortHeight / this.option.height;
}
// 放大
big() {
let zoomRatio = this.canvas.getZoom();
zoomRatio += 0.05;
const center = this.canvas.getCenter();
this.canvas.zoomToPoint(
new fabric.Point(center.left, center.top),
zoomRatio
);
}
// 缩小
small() {
let zoomRatio = this.canvas.getZoom();
zoomRatio -= 0.05;
const center = this.canvas.getCenter();
this.canvas.zoomToPoint(
new fabric.Point(center.left, center.top),
zoomRatio < 0 ? 0.01 : zoomRatio
);
}
// 自动缩放
auto() {
const scale = this._getScale();
this.setZoomAuto(scale - 0.08);
}
// 1:1 放大
one() {
this.setZoomAuto(0.8 - 0.08);
this.canvas.requestRenderAll();
}
_bindWheel() {
this.canvas.on("mouse:wheel", function (this: fabric.Canvas, opt) {
const delta = opt.e.deltaY;
let zoom = this.getZoom();
zoom *= 0.999 ** delta;
if (zoom > 20) zoom = 20;
if (zoom < 0.01) zoom = 0.01;
const center = this.getCenter();
this.zoomToPoint(new fabric.Point(center.left, center.top), zoom);
opt.e.preventDefault();
opt.e.stopPropagation();
});
}
destroy() {
console.log("pluginDestroy");
}
}
export default WorkspacePlugin;

@ -0,0 +1,129 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { fabric } from "fabric";
export function setupGuideLine() {
if (fabric.GuideLine) {
return;
}
fabric.GuideLine = fabric.util.createClass(fabric.Line, {
type: "GuideLine",
selectable: false,
hasControls: false,
hasBorders: false,
stroke: "#4bec13",
originX: "center",
originY: "center",
padding: 4, // 填充,让辅助线选择范围更大,方便选中
globalCompositeOperation: "difference",
axis: "horizontal",
// excludeFromExport: true,
initialize(points, options) {
const isHorizontal = options.axis === "horizontal";
// 指针
this.hoverCursor = isHorizontal ? "ns-resize" : "ew-resize";
// 设置新的点
const newPoints = isHorizontal
? [-999999, points, 999999, points]
: [points, -999999, points, 999999];
// 锁定移动
options[isHorizontal ? "lockMovementX" : "lockMovementY"] = true;
// 调用父类初始化
this.callSuper("initialize", newPoints, options);
// 绑定事件
this.on("mousedown:before", e => {
if (this.activeOn === "down") {
// 设置selectable:false后激活对象才能进行移动
this.canvas.setActiveObject(this, e.e);
}
});
this.on("moving", e => {
if (this.canvas.ruler.options.enabled && this.isPointOnRuler(e.e)) {
this.moveCursor = "not-allowed";
} else {
this.moveCursor = this.isHorizontal() ? "ns-resize" : "ew-resize";
}
this.canvas.fire("guideline:moving", {
target: this,
e: e.e
});
});
this.on("mouseup", e => {
// 移动到标尺上,移除辅助线
if (this.canvas.ruler.options.enabled && this.isPointOnRuler(e.e)) {
// console.log('移除辅助线', this);
this.canvas.remove(this);
return;
}
this.moveCursor = this.isHorizontal() ? "ns-resize" : "ew-resize";
this.canvas.fire("guideline:mouseup", {
target: this,
e: e.e
});
});
this.on("removed", () => {
this.off("removed");
this.off("mousedown:before");
this.off("moving");
this.off("mouseup");
});
},
getBoundingRect(absolute, calculate) {
this.bringToFront();
const isHorizontal = this.isHorizontal();
const rect = this.callSuper("getBoundingRect", absolute, calculate);
rect[isHorizontal ? "top" : "left"] +=
rect[isHorizontal ? "height" : "width"] / 2;
rect[isHorizontal ? "height" : "width"] = 0;
return rect;
},
isPointOnRuler(e) {
const isHorizontal = this.isHorizontal();
const hoveredRuler = this.canvas.ruler.isPointOnRuler(
new fabric.Point(e.offsetX, e.offsetY)
);
if (
(isHorizontal && hoveredRuler === "horizontal") ||
(!isHorizontal && hoveredRuler === "vertical")
) {
return hoveredRuler;
}
return false;
},
isHorizontal() {
return this.height === 0;
}
} as fabric.IGuideLineClassOptions);
fabric.GuideLine.fromObject = function (object, callback) {
const clone = fabric.util.object.clone as (
object: any,
deep: boolean
) => any;
function _callback(instance: any) {
delete instance.xy;
callback && callback(instance);
}
const options = clone(object, true);
const isHorizontal = options.height === 0;
options.xy = isHorizontal ? options.y1 : options.x1;
options.axis = isHorizontal ? "horizontal" : "vertical";
fabric.Object._fromObject(options.type, options, _callback, "xy");
};
}
export default fabric.GuideLine;

@ -0,0 +1,90 @@
import type { Canvas } from "fabric/fabric-impl";
import { fabric } from "fabric";
import CanvasRuler, { RulerOptions } from "./ruler";
function initRuler(canvas: Canvas, options?: RulerOptions) {
const ruler = new CanvasRuler({
canvas,
...options
});
// 辅助线移动到画板外删除
let workspace: fabric.Object | undefined = undefined;
/**
* workspace
*/
const getWorkspace = () => {
workspace = canvas.getObjects().find(item => item.id === "workspace");
};
/**
* targetobject
* @param object
* @param target
* @returns
*/
const isRectOut = (
object: fabric.Object,
target: fabric.GuideLine
): boolean => {
const { top, height, left, width } = object;
if (
top === undefined ||
height === undefined ||
left === undefined ||
width === undefined
) {
return false;
}
const targetRect = target.getBoundingRect(true, true);
const {
top: targetTop,
height: targetHeight,
left: targetLeft,
width: targetWidth
} = targetRect;
if (
target.isHorizontal() &&
(top > targetTop + 1 || top + height < targetTop + targetHeight - 1)
) {
return true;
} else if (
!target.isHorizontal() &&
(left > targetLeft + 1 || left + width < targetLeft + targetWidth - 1)
) {
return true;
}
return false;
};
canvas.on("guideline:moving", e => {
if (!workspace) {
getWorkspace();
return;
}
const { target } = e;
if (isRectOut(workspace, target)) {
target.moveCursor = "not-allowed";
}
});
canvas.on("guideline:mouseup", e => {
if (!workspace) {
getWorkspace();
return;
}
const { target } = e;
if (isRectOut(workspace, target)) {
canvas.remove(target);
canvas.setCursor(canvas.defaultCursor ?? "");
}
});
return ruler;
}
export default initRuler;

@ -0,0 +1,655 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Canvas, Point, IEvent } from "fabric/fabric-impl";
import { fabric } from "fabric";
import {
getGap,
mergeLines,
darwRect,
darwText,
darwLine,
drawMask
} from "./utils";
import { throttle } from "lodash-es";
import { setupGuideLine } from "./guideline";
/**
*
*/
export interface RulerOptions {
/**
* Canvas
*/
canvas: Canvas;
/**
*
* @default 20
*/
ruleSize?: number;
/**
*
* @default 10
*/
fontSize?: number;
/**
*
* @default false
*/
enabled?: boolean;
/**
*
*/
backgroundColor?: string;
/**
*
*/
textColor?: string;
/**
*
*/
borderColor?: string;
/**
*
*/
highlightColor?: string;
}
export type Rect = { left: number; top: number; width: number; height: number };
export type HighlightRect = {
skip?: "x" | "y";
} & Rect;
class CanvasRuler {
protected ctx: CanvasRenderingContext2D;
/**
*
*/
public options: Required<RulerOptions>;
/**
*
*/
public startCalibration: undefined | Point;
private activeOn: "down" | "up" = "up";
/**
*
*/
private objectRect:
| undefined
| {
x: HighlightRect[];
y: HighlightRect[];
};
/**
*
*/
private eventHandler: Record<string, (...args: any) => void> = {
// calcCalibration: this.calcCalibration.bind(this),
calcObjectRect: throttle(this.calcObjectRect.bind(this), 15),
clearStatus: this.clearStatus.bind(this),
canvasMouseDown: this.canvasMouseDown.bind(this),
canvasMouseMove: throttle(this.canvasMouseMove.bind(this), 15),
canvasMouseUp: this.canvasMouseUp.bind(this),
render: (e: any) => {
// 避免多次渲染
if (!e.ctx) return;
this.render();
}
};
private lastAttr: {
status: "out" | "horizontal" | "vertical";
cursor: string | undefined;
selection: boolean | undefined;
} = {
status: "out",
cursor: undefined,
selection: undefined
};
private tempGuidelLine: fabric.GuideLine | undefined;
constructor(_options: RulerOptions) {
// 合并默认配置
this.options = Object.assign(
{
ruleSize: 20,
fontSize: 10,
enabled: false,
backgroundColor: "#fff",
borderColor: "#ddd",
highlightColor: "#007fff",
textColor: "#888"
},
_options
);
this.ctx = this.options.canvas.getContext();
fabric.util.object.extend(this.options.canvas, {
ruler: this
});
setupGuideLine();
if (this.options.enabled) {
this.enable();
}
}
// 销毁
public destroy() {
this.disable();
}
/**
* 线
*/
public clearGuideline() {
this.options.canvas.remove(
...this.options.canvas.getObjects(fabric.GuideLine.prototype.type)
);
}
/**
* 线
*/
public showGuideline() {
this.options.canvas
.getObjects(fabric.GuideLine.prototype.type)
.forEach(guideLine => {
guideLine.set("visible", true);
});
this.options.canvas.renderAll();
}
/**
* 线
*/
public hideGuideline() {
this.options.canvas
.getObjects(fabric.GuideLine.prototype.type)
.forEach(guideLine => {
guideLine.set("visible", false);
});
this.options.canvas.renderAll();
}
/**
*
*/
public enable() {
this.options.enabled = true;
// 绑定事件
this.options.canvas.on("after:render", this.eventHandler.calcObjectRect);
this.options.canvas.on("after:render", this.eventHandler.render);
this.options.canvas.on("mouse:down", this.eventHandler.canvasMouseDown);
this.options.canvas.on("mouse:move", this.eventHandler.canvasMouseMove);
this.options.canvas.on("mouse:up", this.eventHandler.canvasMouseUp);
this.options.canvas.on("selection:cleared", this.eventHandler.clearStatus);
// 显示辅助线
this.showGuideline();
// 绘制一次
this.render();
}
/**
*
*/
public disable() {
// 解除事件
this.options.canvas.off("after:render", this.eventHandler.calcObjectRect);
this.options.canvas.off("after:render", this.eventHandler.render);
this.options.canvas.off("mouse:down", this.eventHandler.canvasMouseDown);
this.options.canvas.off("mouse:move", this.eventHandler.canvasMouseMove);
this.options.canvas.off("mouse:up", this.eventHandler.canvasMouseUp);
this.options.canvas.off("selection:cleared", this.eventHandler.clearStatus);
// 隐藏辅助线
this.hideGuideline();
this.options.enabled = false;
}
/**
*
*/
public render() {
// if (!this.options.enabled) return;
const vpt = this.options.canvas.viewportTransform;
if (!vpt) return;
// 绘制尺子
this.draw({
isHorizontal: true,
rulerLength: this.getSize().width,
// startCalibration: -(vpt[4] / vpt[0]),
startCalibration: this.startCalibration?.x
? this.startCalibration.x
: -(vpt[4] / vpt[0])
});
this.draw({
isHorizontal: false,
rulerLength: this.getSize().height,
// startCalibration: -(vpt[5] / vpt[3]),
startCalibration: this.startCalibration?.y
? this.startCalibration.y
: -(vpt[5] / vpt[3])
});
// 绘制左上角的遮罩
drawMask(this.ctx, {
isHorizontal: true,
left: -10,
top: -10,
width: this.options.ruleSize * 2 + 10,
height: this.options.ruleSize + 10,
backgroundColor: this.options.backgroundColor
});
drawMask(this.ctx, {
isHorizontal: false,
left: -10,
top: -10,
width: this.options.ruleSize + 10,
height: this.options.ruleSize * 2 + 10,
backgroundColor: this.options.backgroundColor
});
}
/**
*
*/
private getSize() {
return {
width: this.options.canvas.width ?? 0,
height: this.options.canvas.height ?? 0
};
}
private getZoom() {
return this.options.canvas.getZoom();
}
private draw(opt: {
isHorizontal: boolean;
rulerLength: number;
startCalibration: number;
}) {
const { isHorizontal, rulerLength, startCalibration } = opt;
const zoom = this.getZoom();
const gap = getGap(zoom);
const unitLength = rulerLength / zoom;
const startValue =
Math[startCalibration > 0 ? "floor" : "ceil"](startCalibration / gap) *
gap;
const startOffset = startValue - startCalibration;
// 标尺背景
const canvasSize = this.getSize();
darwRect(this.ctx, {
left: 0,
top: 0,
width: isHorizontal ? canvasSize.width : this.options.ruleSize,
height: isHorizontal ? this.options.ruleSize : canvasSize.height,
fill: this.options.backgroundColor,
stroke: this.options.borderColor
});
// 颜色
const textColor = new fabric.Color(this.options.textColor);
// 标尺文字显示
for (let i = 0; i + startOffset <= Math.ceil(unitLength); i += gap) {
const position = (startOffset + i) * zoom;
const textValue = startValue + i + "";
const textLength = (10 * textValue.length) / 4;
const textX = isHorizontal
? position - textLength - 1
: this.options.ruleSize / 2 - this.options.fontSize / 2 - 4;
const textY = isHorizontal
? this.options.ruleSize / 2 - this.options.fontSize / 2 - 4
: position + textLength;
darwText(this.ctx, {
text: textValue,
left: textX,
top: textY,
fill: textColor.toRgb(),
angle: isHorizontal ? 0 : -90
});
}
// 标尺刻度线显示
for (let j = 0; j + startOffset <= Math.ceil(unitLength); j += gap) {
const position = Math.round((startOffset + j) * zoom);
const left = isHorizontal ? position : this.options.ruleSize - 8;
const top = isHorizontal ? this.options.ruleSize - 8 : position;
const width = isHorizontal ? 0 : 8;
const height = isHorizontal ? 8 : 0;
darwLine(this.ctx, {
left,
top,
width,
height,
stroke: textColor.toRgb()
});
}
// 标尺蓝色遮罩
if (this.objectRect) {
const axis = isHorizontal ? "x" : "y";
this.objectRect[axis].forEach(rect => {
// 跳过指定矩形
if (rect.skip === axis) {
return;
}
// 获取数字的值
const roundFactor = (x: number) =>
Math.round(x / zoom + startCalibration) + "";
const leftTextVal = roundFactor(isHorizontal ? rect.left : rect.top);
const rightTextVal = roundFactor(
isHorizontal ? rect.left + rect.width : rect.top + rect.height
);
const isSameText = leftTextVal === rightTextVal;
// 背景遮罩
const maskOpt = {
isHorizontal,
width: isHorizontal ? 160 : this.options.ruleSize - 8,
height: isHorizontal ? this.options.ruleSize - 8 : 160,
backgroundColor: this.options.backgroundColor
};
drawMask(this.ctx, {
...maskOpt,
left: isHorizontal ? rect.left - 80 : 0,
top: isHorizontal ? 0 : rect.top - 80
});
if (!isSameText) {
drawMask(this.ctx, {
...maskOpt,
left: isHorizontal ? rect.width + rect.left - 80 : 0,
top: isHorizontal ? 0 : rect.height + rect.top - 80
});
}
// 颜色
const highlightColor = new fabric.Color(this.options.highlightColor);
// 高亮遮罩
highlightColor.setAlpha(0.5);
darwRect(this.ctx, {
left: isHorizontal ? rect.left : this.options.ruleSize - 8,
top: isHorizontal ? this.options.ruleSize - 8 : rect.top,
width: isHorizontal ? rect.width : 8,
height: isHorizontal ? 8 : rect.height,
fill: highlightColor.toRgba()
});
// 两边的数字
const pad = this.options.ruleSize / 2 - this.options.fontSize / 2 - 4;
const textOpt = {
fill: highlightColor.toRgba(),
angle: isHorizontal ? 0 : -90
};
darwText(this.ctx, {
...textOpt,
text: leftTextVal,
left: isHorizontal ? rect.left - 2 : pad,
top: isHorizontal ? pad : rect.top - 2,
align: isSameText ? "center" : isHorizontal ? "right" : "left"
});
if (!isSameText) {
darwText(this.ctx, {
...textOpt,
text: rightTextVal,
left: isHorizontal ? rect.left + rect.width + 2 : pad,
top: isHorizontal ? pad : rect.top + rect.height + 2,
align: isHorizontal ? "left" : "right"
});
}
// 两边的线
const lineSize = isSameText ? 8 : 14;
highlightColor.setAlpha(1);
const lineOpt = {
width: isHorizontal ? 0 : lineSize,
height: isHorizontal ? lineSize : 0,
stroke: highlightColor.toRgba()
};
darwLine(this.ctx, {
...lineOpt,
left: isHorizontal ? rect.left : this.options.ruleSize - lineSize,
top: isHorizontal ? this.options.ruleSize - lineSize : rect.top
});
if (!isSameText) {
darwLine(this.ctx, {
...lineOpt,
left: isHorizontal
? rect.left + rect.width
: this.options.ruleSize - lineSize,
top: isHorizontal
? this.options.ruleSize - lineSize
: rect.top + rect.height
});
}
});
}
// draw end
}
/**
*
*/
// private calcCalibration() {
// if (this.startCalibration) return;
// // console.log('calcCalibration');
// const workspace = this.options.canvas.getObjects().find((item: any) => {
// return item.id === 'workspace';
// });
// if (!workspace) return;
// const rect = workspace.getBoundingRect(false);
// this.startCalibration = new fabric.Point(-rect.left, -rect.top).divide(this.getZoom());
// }
private calcObjectRect() {
const activeObjects = this.options.canvas.getActiveObjects();
if (activeObjects.length === 0) return;
const allRect = activeObjects.reduce((rects, obj) => {
const rect: HighlightRect = obj.getBoundingRect(false, true);
// 如果是分组单独计算坐标
if (obj.group) {
const group = {
top: 0,
left: 0,
width: 0,
height: 0,
scaleX: 1,
scaleY: 1,
...obj.group
};
// 计算矩形坐标
rect.width *= group.scaleX;
rect.height *= group.scaleY;
const groupCenterX = group.width / 2 + group.left;
const objectOffsetFromCenterX =
(group.width / 2 + (obj.left ?? 0)) * (1 - group.scaleX);
rect.left += (groupCenterX - objectOffsetFromCenterX) * this.getZoom();
const groupCenterY = group.height / 2 + group.top;
const objectOffsetFromCenterY =
(group.height / 2 + (obj.top ?? 0)) * (1 - group.scaleY);
rect.top += (groupCenterY - objectOffsetFromCenterY) * this.getZoom();
}
if (obj instanceof fabric.GuideLine) {
rect.skip = obj.isHorizontal() ? "x" : "y";
}
rects.push(rect);
return rects;
}, [] as HighlightRect[]);
if (allRect.length === 0) return;
this.objectRect = {
x: mergeLines(allRect, true),
y: mergeLines(allRect, false)
};
}
/**
*
*/
private clearStatus() {
// this.startCalibration = undefined;
this.objectRect = undefined;
}
/**
* @param point
* @returns "vertical" | "horizontal" | false
*/
public isPointOnRuler(point: Point) {
if (
new fabric.Rect({
left: 0,
top: 0,
width: this.options.ruleSize,
height: this.options.canvas.height
}).containsPoint(point)
) {
return "vertical";
} else if (
new fabric.Rect({
left: 0,
top: 0,
width: this.options.canvas.width,
height: this.options.ruleSize
}).containsPoint(point)
) {
return "horizontal";
}
return false;
}
private canvasMouseDown(e: IEvent<MouseEvent>) {
if (!e.pointer || !e.absolutePointer) return;
const hoveredRuler = this.isPointOnRuler(e.pointer);
if (hoveredRuler && this.activeOn === "up") {
// 备份属性
this.lastAttr.selection = this.options.canvas.selection;
this.options.canvas.selection = false;
this.activeOn = "down";
this.tempGuidelLine = new fabric.GuideLine(
hoveredRuler === "horizontal"
? e.absolutePointer.y
: e.absolutePointer.x,
{
axis: hoveredRuler,
visible: false
}
);
this.options.canvas.add(this.tempGuidelLine);
this.options.canvas.setActiveObject(this.tempGuidelLine);
this.options.canvas._setupCurrentTransform(
e.e,
this.tempGuidelLine,
true
);
this.tempGuidelLine.fire("down", this.getCommonEventInfo(e));
}
}
private getCommonEventInfo = (e: IEvent<MouseEvent>) => {
if (!this.tempGuidelLine || !e.absolutePointer) return;
return {
e: e.e,
transform: this.tempGuidelLine.get("transform"),
pointer: {
x: e.absolutePointer.x,
y: e.absolutePointer.y
},
target: this.tempGuidelLine
};
};
private canvasMouseMove(e: IEvent<MouseEvent>) {
if (!e.pointer) return;
if (this.tempGuidelLine && e.absolutePointer) {
const pos: Partial<fabric.IGuideLineOptions> = {};
if (this.tempGuidelLine.axis === "horizontal") {
pos.top = e.absolutePointer.y;
} else {
pos.left = e.absolutePointer.x;
}
this.tempGuidelLine.set({ ...pos, visible: true });
this.options.canvas.requestRenderAll();
const event = this.getCommonEventInfo(e);
this.options.canvas.fire("object:moving", event);
this.tempGuidelLine.fire("moving", event);
}
const hoveredRuler = this.isPointOnRuler(e.pointer);
if (!hoveredRuler) {
// 鼠标从里面出去
if (this.lastAttr.status !== "out") {
// 更改鼠标指针
this.options.canvas.defaultCursor = this.lastAttr.cursor;
this.lastAttr.status = "out";
}
return;
}
// const activeObjects = this.options.canvas.getActiveObjects();
// if (activeObjects.length === 1 && activeObjects[0] instanceof fabric.GuideLine) {
// return;
// }
// 鼠标从外边进入 或 在另一侧标尺
if (
this.lastAttr.status === "out" ||
hoveredRuler !== this.lastAttr.status
) {
// 更改鼠标指针
this.lastAttr.cursor = this.options.canvas.defaultCursor;
this.options.canvas.defaultCursor =
hoveredRuler === "horizontal" ? "ns-resize" : "ew-resize";
this.lastAttr.status = hoveredRuler;
}
}
private canvasMouseUp(e: IEvent<MouseEvent>) {
if (this.activeOn !== "down") return;
// 还原属性
this.options.canvas.selection = this.lastAttr.selection;
this.activeOn = "up";
this.tempGuidelLine?.fire("up", this.getCommonEventInfo(e));
this.tempGuidelLine = undefined;
}
}
export default CanvasRuler;

@ -0,0 +1,62 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type CanvasRuler, { Rect } from "./ruler";
declare module "fabric/fabric-impl" {
type EventNameExt = "removed" | EventName;
export interface Canvas {
_setupCurrentTransform(
e: Event,
target: fabric.Object,
alreadySelected: boolean
): void;
}
export interface IObservable<T> {
on(
eventName: "guideline:moving" | "guideline:mouseup",
handler: (event: { e: Event; target: fabric.GuideLine }) => void
): T;
on(events: {
[key: EventName]: (event: { e: Event; target: fabric.GuideLine }) => void;
}): T;
}
export interface IGuideLineOptions extends ILineOptions {
axis: "horizontal" | "vertical";
}
export interface IGuideLineClassOptions extends IGuideLineOptions {
canvas: {
setActiveObject(
object: fabric.Object | fabric.GuideLine,
e?: Event
): Canvas;
remove<T>(...object: (fabric.Object | fabric.GuideLine)[]): T;
} & Canvas;
activeOn: "down" | "up";
initialize(xy: number, objObjects: IGuideLineOptions): void;
callSuper(methodName: string, ...args: unknown[]): any;
getBoundingRect(absolute?: boolean, calculate?: boolean): Rect;
on(eventName: EventNameExt, handler: (e: IEvent<MouseEvent>) => void): void;
off(
eventName: EventNameExt,
handler?: (e: IEvent<MouseEvent>) => void
): void;
fire<T>(eventName: EventNameExt, options?: any): T;
isPointOnRuler(e: MouseEvent): "horizontal" | "vertical" | false;
bringToFront(): fabric.Object;
isHorizontal(): boolean;
}
export interface GuideLine extends Line, IGuideLineClassOptions {}
export class GuideLine extends Line {
constructor(xy: number, objObjects?: IGuideLineOptions);
static fromObject(object: any, callback: any): void;
}
export interface StaticCanvas {
ruler: InstanceType<typeof CanvasRuler>;
}
}

@ -0,0 +1,162 @@
import type { Rect } from "./ruler";
import { fabric } from "fabric";
/**
*
* @param zoom
* @returns
*/
const getGap = (zoom: number) => {
const zooms = [0.02, 0.03, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 18];
const gaps = [5000, 2500, 1000, 500, 250, 100, 50, 25, 10, 5, 2];
let i = 0;
while (i < zooms.length && zooms[i] < zoom) {
i++;
}
return gaps[i - 1] || 5000;
};
/**
* 线
* @param rect Rect
* @param isHorizontal
* @returns Rect
*/
const mergeLines = (rect: Rect[], isHorizontal: boolean) => {
const axis = isHorizontal ? "left" : "top";
const length = isHorizontal ? "width" : "height";
// 先按照 axis 的大小排序
rect.sort((a, b) => a[axis] - b[axis]);
const mergedLines = [];
let currentLine = Object.assign({}, rect[0]);
for (const item of rect) {
const line = Object.assign({}, item);
if (currentLine[axis] + currentLine[length] >= line[axis]) {
// 当前线段和下一个线段相交,合并宽度
currentLine[length] =
Math.max(
currentLine[axis] + currentLine[length],
line[axis] + line[length]
) - currentLine[axis];
} else {
// 当前线段和下一个线段不相交,将当前线段加入结果数组中,并更新当前线段为下一个线段
mergedLines.push(currentLine);
currentLine = Object.assign({}, line);
}
}
// 加入数组
mergedLines.push(currentLine);
return mergedLines;
};
const darwLine = (
ctx: CanvasRenderingContext2D,
options: {
left: number;
top: number;
width: number;
height: number;
stroke?: string | CanvasGradient | CanvasPattern;
lineWidth?: number;
}
) => {
ctx.save();
const { left, top, width, height, stroke, lineWidth } = options;
ctx.beginPath();
stroke && (ctx.strokeStyle = stroke);
ctx.lineWidth = lineWidth ?? 1;
ctx.moveTo(left, top);
ctx.lineTo(left + width, top + height);
ctx.stroke();
ctx.restore();
};
const darwText = (
ctx: CanvasRenderingContext2D,
options: {
left: number;
top: number;
text: string;
fill?: string | CanvasGradient | CanvasPattern;
align?: CanvasTextAlign;
angle?: number;
fontSize?: number;
}
) => {
ctx.save();
const { left, top, text, fill, align, angle, fontSize } = options;
fill && (ctx.fillStyle = fill);
ctx.textAlign = align ?? "left";
ctx.textBaseline = "top";
ctx.font = `${fontSize ?? 10}px sans-serif`;
if (angle) {
ctx.translate(left, top);
ctx.rotate((Math.PI / 180) * angle);
ctx.translate(-left, -top);
}
ctx.fillText(text, left, top);
ctx.restore();
};
const darwRect = (
ctx: CanvasRenderingContext2D,
options: {
left: number;
top: number;
width: number;
height: number;
fill?: string | CanvasGradient | CanvasPattern;
stroke?: string;
strokeWidth?: number;
}
) => {
ctx.save();
const { left, top, width, height, fill, stroke, strokeWidth } = options;
ctx.beginPath();
fill && (ctx.fillStyle = fill);
ctx.rect(left, top, width, height);
ctx.fill();
if (stroke) {
ctx.strokeStyle = stroke;
ctx.lineWidth = strokeWidth ?? 1;
ctx.stroke();
}
ctx.restore();
};
const drawMask = (
ctx: CanvasRenderingContext2D,
options: {
isHorizontal: boolean;
left: number;
top: number;
width: number;
height: number;
backgroundColor: string;
}
) => {
ctx.save();
const { isHorizontal, left, top, width, height, backgroundColor } = options;
// 创建一个线性渐变对象
const gradient = isHorizontal
? ctx.createLinearGradient(left, height / 2, left + width, height / 2)
: ctx.createLinearGradient(width / 2, top, width / 2, height + top);
const transparentColor = new fabric.Color(backgroundColor);
transparentColor.setAlpha(0);
gradient.addColorStop(0, transparentColor.toRgba());
gradient.addColorStop(0.33, backgroundColor);
gradient.addColorStop(0.67, backgroundColor);
gradient.addColorStop(1, transparentColor.toRgba());
darwRect(ctx, {
left,
top,
width,
height,
fill: gradient
});
ctx.restore();
};
export { getGap, mergeLines, darwRect, darwText, darwLine, drawMask };

@ -0,0 +1,8 @@
export const customJsonAttr: string[] = [
"id",
"gradientAngle",
"selectable",
"hasControls",
"userProperty",
"animation" // 动画
];

@ -0,0 +1,24 @@
/*
* @Author: zhoux zhouxia@supervision.ltd
* @Date: 2023-11-30 16:01:52
* @LastEditors: zhoux zhouxia@supervision.ltd
* @LastEditTime: 2023-11-30 16:02:47
* @FilePath: \vue-fabric-editor\src\hooks\handlers\CustomHandler.ts
* @Description:
*/
import { Handler } from "./Handler";
class CustomHandler {
handler: Handler;
constructor(handler: Handler) {
this.handler = handler;
this.initialze();
}
protected initialze() {
// empty
}
}
export default CustomHandler;

@ -0,0 +1,299 @@
/*
* @Author: zhoux zhouxia@supervision.ltd
* @Date: 2023-11-30 15:59:04
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:06:57
* @FilePath: \vue-fabric-editor\src\hooks\handlers\Handler.ts
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
// import AnimationHandler from './AnimationHandler';
import CustomHandler from "./CustomHandler";
import { FabricCanvas, FabricObject, FabricObjects } from "./typing";
export interface HandlerCallback {
/**
* When has been added object in Canvas, Called function
*
*/
onAdd?: (object: FabricObject) => void;
/**
* Return contextmenu element
*
*/
// onContext?: (el: HTMLDivElement, e: React.MouseEvent, target?: FabricObject) => Promise<any> | any;
/**
* Return tooltip element
*
*/
onTooltip?: (el: HTMLDivElement, target?: FabricObject) => Promise<any> | any;
/**
* When zoom, Called function
*/
onZoom?: (zoomRatio: number) => void;
/**
* When clicked object, Called function
*
*/
onClick?: (canvas: FabricCanvas, target: FabricObject) => void;
/**
* When double clicked object, Called function
*
*/
onDblClick?: (canvas: FabricCanvas, target: FabricObject) => void;
/**
* When modified object, Called function
*/
onModified?: (target: FabricObject) => void;
/**
* When select object, Called function
*
*/
onSelect?: (target: FabricObject) => void;
/**
* When has been removed object in Canvas, Called function
*
*/
onRemove?: (target: FabricObject) => void;
/**
* When has been undo or redo, Called function
*
*/
// onTransaction?: (transaction: TransactionEvent) => void;
/**
* When has been changed interaction mode, Called function
*
*/
// onInteraction?: (interactionMode: InteractionMode) => void;
/**
* When canvas has been loaded
*
*/
onLoad?: (handler: Handler, canvas?: fabric.Canvas) => void;
}
export interface HandlerOption {
/**
* Canvas id
* @type {string}
*/
id?: string;
/**
* Canvas object
* @type {FabricCanvas}
*/
canvas?: FabricCanvas;
/**
* Canvas parent element
* @type {HTMLDivElement}
*/
container?: HTMLDivElement;
/**
* Canvas editable
* @type {boolean}
*/
editable?: boolean;
/**
* Canvas interaction mode
* @type {InteractionMode}
*/
// interactionMode?: InteractionMode;
/**
* Persist properties for object
* @type {string[]}
*/
propertiesToInclude?: string[];
/**
* Minimum zoom ratio
* @type {number}
*/
minZoom?: number;
/**
* Maximum zoom ratio
* @type {number}
*/
maxZoom?: number;
/**
* Zoom ratio step
* @type {number}
*/
zoomStep?: number;
/**
* Workarea option
* @type {WorkareaOption}
*/
// workareaOption?: WorkareaOption;
/**
* Canvas option
* @type {CanvasOption}
*/
// canvasOption?: CanvasOption;
/**
* Grid option
* @type {GridOption}
*/
// gridOption?: GridOption;
/**
* Default option for Fabric Object
* @type {FabricObjectOption}
*/
// objectOption?: FabricObjectOption;
/**
* Guideline option
* @type {GuidelineOption}
*/
// guidelineOption?: GuidelineOption;
/**
* Whether to use zoom
* @type {boolean}
*/
zoomEnabled?: boolean;
/**
* ActiveSelection option
* @type {Partial<FabricObjectOption<fabric.ActiveSelection>>}
*/
// activeSelectionOption?: Partial<FabricObjectOption<fabric.ActiveSelection>>;
/**
* Canvas width
* @type {number}
*/
width?: number;
/**
* Canvas height
* @type {number}
*/
height?: number;
/**
* Keyboard event in Canvas
* @type {KeyEvent}
*/
// keyEvent?: KeyEvent;
/**
* Append custom objects
* @type {{ [key: string]: any }}
*/
fabricObjects?: FabricObjects;
handlers?: { [key: string]: CustomHandler };
[key: string]: any;
}
export type HandlerOptions = HandlerOption & HandlerCallback;
class Handler implements HandlerOptions {
public id: string | undefined;
public canvas: FabricCanvas | undefined;
public onAdd?: (object: FabricObject) => void;
// public onContext?: (
// el: HTMLDivElement,
// e: React.MouseEvent,
// target?: FabricObject
// ) => Promise<any>;
public onTooltip?: (
el: HTMLDivElement,
target?: FabricObject
) => Promise<any>;
public onZoom?: (zoomRatio: number) => void;
public onClick?: (canvas: FabricCanvas, target: FabricObject) => void;
public onDblClick?: (canvas: FabricCanvas, target: FabricObject) => void;
public onModified?: (target: FabricObject) => void;
public onSelect?: (target: FabricObject) => void;
public onRemove?: (target: FabricObject) => void;
// public onTransaction?: (transaction: TransactionEvent) => void;
// public onInteraction?: (interactionMode: InteractionMode) => void;
public onLoad?: (handler: Handler, canvas?: fabric.Canvas) => void;
// public animationHandler!: AnimationHandler;
constructor(options: HandlerOptions) {
this.initialize(options);
}
/**
* Initialize handler
*
* @author salgum1114
* @param {HandlerOptions} options
*/
public initialize(options: HandlerOptions) {
this.initOption(options);
this.initCallback(options);
this.initHandler();
}
/**
* Init class fields
* @param {HandlerOptions} options
*/
public initOption = (options: HandlerOptions) => {
this.id = options.id;
this.canvas = options.canvas;
// this.container = options.container;
// this.editable = options.editable;
// this.interactionMode = options.interactionMode;
// this.minZoom = options.minZoom;
// this.maxZoom = options.maxZoom;
// this.zoomStep = options.zoomStep || 0.05;
// this.zoomEnabled = options.zoomEnabled;
// this.width = options.width;
// this.height = options.height;
// this.objects = [];
// this.setPropertiesToInclude(options.propertiesToInclude);
// this.setWorkareaOption(options.workareaOption);
// this.setCanvasOption(options.canvasOption);
// this.setGridOption(options.gridOption);
// this.setObjectOption(options.objectOption);
// this.setFabricObjects(options.fabricObjects);
// this.setGuidelineOption(options.guidelineOption);
// this.setActiveSelectionOption(options.activeSelectionOption);
// this.setKeyEvent(options.keyEvent);
};
/**
* Initialize callback
* @param {HandlerOptions} options
*/
public initCallback = (options: HandlerOptions) => {
this.onAdd = options.onAdd;
this.onTooltip = options.onTooltip;
this.onZoom = options.onZoom;
// this.onContext = options.onContext;
this.onClick = options.onClick;
this.onModified = options.onModified;
this.onDblClick = options.onDblClick;
this.onSelect = options.onSelect;
this.onRemove = options.onRemove;
// this.onTransaction = options.onTransaction;
// this.onInteraction = options.onInteraction;
this.onLoad = options.onLoad;
};
/**
* Initialize handlers
*
*/
public initHandler = () => {
// this.animationHandler = new AnimationHandler(this);
};
/**
* Find object by id
* @param {string} id
* @returns {(FabricObject | null)}
*/
public findById = (id: string): FabricObject | null => {
let findObject;
const exist = this.objects.some(obj => {
if (obj.id === id) {
findObject = obj;
return true;
}
return false;
});
if (!exist) {
warning(true, "Not found object by id.");
return null;
}
return findObject;
};
}

@ -0,0 +1,156 @@
export interface FabricCanvasOption {
wrapperEl?: HTMLElement;
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint
export type FabricCanvas<T extends any = fabric.Canvas> = T &
FabricCanvasOption;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint
export type FabricObjectOption<T extends any = fabric.IObjectOptions> = T & {
/**
* Object id
* @type {string}
*/
id?: string;
/**
* Parent object id
* @type {string}
*/
parentId?: string;
/**
* Original opacity
* @type {number}
*/
originOpacity?: number;
/**
* Original top position
* @type {number}
*/
originTop?: number;
/**
* Original left position
* @type {number}
*/
originLeft?: number;
/**
* Original scale X
* @type {number}
*/
originScaleX?: number;
/**
* Original scale Y
* @type {number}
*/
originScaleY?: number;
/**
* Original angle
* @type {number}
*/
originAngle?: number;
/**
* Original fill color
*
* @type {(string | fabric.Pattern | fabric.Gradient)}
*/
originFill?: string | fabric.Pattern | fabric.Gradient;
/**
* Original stroke color
* @type {string}
*/
originStroke?: string;
/**
* Original rotation
*
* @type {number}
*/
originRotation?: number;
/**
* Object editable
* @type {boolean}
*/
editable?: boolean;
/**
* Object Super type
* @type {string}
*/
superType?: string;
/**
* @description
* @type {string}
*/
description?: string;
/**
* Animation property
* @type {AnimationProperty}
*/
animation?: AnimationProperty;
/**
* Anime instance
* @type {anime.AnimeInstance}
*/
anime?: anime.AnimeInstance;
/**
* Tooltip property
* @type {TooltipProperty}
*/
tooltip?: TooltipProperty;
/**
* Link property
* @type {LinkProperty}
*/
link?: LinkProperty;
/**
* Is running animation
* @type {boolean}
*/
animating?: boolean;
/**
* Object class
* @type {string}
*/
class?: string;
/**
* Is possible delete
* @type {boolean}
*/
deletable?: boolean;
/**
* Is enable double click
* @type {boolean}
*/
dblclick?: boolean;
/**
* Is possible clone
* @type {boolean}
*/
cloneable?: boolean;
/**
* Is locked object
* @type {boolean}
*/
locked?: boolean;
/**
* This property replaces "angle"
*
* @type {number}
*/
rotation?: number;
/**
* Whether it can be clicked
*
* @type {boolean}
*/
clickable?: boolean;
[key: string]: any;
};
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint
export type FabricObject<T extends any = fabric.Object> = T &
FabricObjectOption;
export type FabricObjects = {
[key: string]: {
create: (...args: any) => FabricObject;
};
};

@ -0,0 +1,72 @@
/*
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-08-06 16:50:48
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 17:07:30
* @FilePath: \General-AI-Platform-Web-Client\src\hooks\select.ts
* @Description: useSelect
*/
import { inject, onBeforeMount, onMounted, reactive } from "vue";
import { SelectEvent, SelectMode, SelectOneType } from "@/utils/event/types";
interface Selector {
mSelectMode: SelectMode;
mSelectOneType: SelectOneType;
mSelectId: string[] | "";
mSelectIds: string[];
mSelectActive: unknown[];
}
export default function useSelect() {
const state = reactive<Selector>({
mSelectMode: SelectMode.EMPTY,
mSelectOneType: SelectOneType.EMPTY,
mSelectId: "", // 选择id
mSelectIds: [], // 选择id
mSelectActive: []
});
const fabric = inject("fabric");
// const canvas = inject('canvas');
const canvasEditor = inject("canvasEditor");
const event = inject("event");
const selectOne = e => {
state.mSelectMode = SelectMode.ONE;
state.mSelectId = e[0].id;
state.mSelectOneType = e[0].type;
state.mSelectIds = e.map(item => item.id);
};
const selectMulti = e => {
state.mSelectMode = SelectMode.MULTI;
state.mSelectId = "";
state.mSelectIds = e.map(item => item.id);
};
const selectCancel = () => {
state.mSelectId = "";
state.mSelectIds = [];
state.mSelectMode = SelectMode.EMPTY;
state.mSelectOneType = SelectOneType.EMPTY;
};
onMounted(() => {
event.on(SelectEvent.ONE, selectOne);
event.on(SelectEvent.MULTI, selectMulti);
event.on(SelectEvent.CANCEL, selectCancel);
});
onBeforeMount(() => {
event.off(SelectEvent.ONE, selectOne);
event.off(SelectEvent.MULTI, selectMulti);
event.off(SelectEvent.CANCEL, selectCancel);
});
return {
fabric,
// canvas,
canvasEditor,
mixinState: state
};
}

@ -517,7 +517,7 @@ onBeforeUnmount(() => {
<IconifyIconOffline :icon="ArrowLeftSLine" @click="handleScroll(200)" />
</span>
<div ref="scrollbarDom" class="scroll-container">
<div class="tab select-none" ref="tabDom" :style="getTabStyle">
<div class="select-none tab" ref="tabDom" :style="getTabStyle">
<div
:ref="'dynamic' + index"
v-for="(item, index) in multiTags"

@ -2,7 +2,7 @@
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-02-22 13:38:04
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-02-29 10:18:23
* @LastEditTime: 2024-08-07 09:33:51
* @FilePath: \General-AI-Platform-Web-Client\src\main.ts
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
@ -30,6 +30,7 @@ import "element-plus/dist/index.css";
// 导入字体图标
import "./assets/iconfont/iconfont.js";
import "./assets/iconfont/iconfont.css";
import VueLazyLoad from "vue3-lazyload";
const app = createApp(App);
@ -54,7 +55,7 @@ import { Auth } from "@/components/ReAuth";
app.component("Auth", Auth);
getServerConfig(app).then(async config => {
app.use(router);
app.use(router).use(VueLazyLoad, {});
await router.isReady();
injectResponsiveStorage(app, config);
setupStore(app);

@ -140,7 +140,7 @@ const handleClickDetail = (modelData: CardProductType) => {
rgba(255, 207, 95, 0.4) 0%,
rgba(255, 207, 95, 0) 100%
);
background-image: url("@/assets/dataScreen/modelList/inUsed.svg");
// background-image: url("@/assets/dataScreen/modelList/inUsedBg.svg");
background-repeat: no-repeat;
cursor: pointer;
.model-box-state {

@ -0,0 +1,54 @@
/*
* @Author:
* @Date: 2022-09-03 19:16:55
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 16:44:50
* @Description:
*/
import EventEmitter from "events";
import { fabric } from "fabric";
import { Canvas } from "fabric/fabric-impl";
import { SelectEvent } from "@/utils/event/types";
/**
*
*/
class CanvasEventEmitter extends EventEmitter {
handler: Canvas | undefined;
mSelectMode = "";
init(handler: CanvasEventEmitter["handler"]) {
this.handler = handler;
if (this.handler) {
this.handler.on("selection:created", () => this.selected());
this.handler.on("selection:updated", () => this.selected());
this.handler.on("selection:cleared", () => this.selected());
}
}
/**
*
* @private
*/
private selected() {
if (!this.handler) {
throw TypeError("还未初始化");
}
const actives = this.handler
.getActiveObjects()
.filter(item => !(item instanceof fabric.GuideLine)); // 过滤掉辅助线
if (actives && actives.length === 1) {
this.emit(SelectEvent.ONE, actives);
} else if (actives && actives.length > 1) {
this.mSelectMode = "multiple";
this.emit(SelectEvent.MULTI, actives);
} else {
this.emit(SelectEvent.CANCEL);
}
}
}
export default new CanvasEventEmitter();
export { CanvasEventEmitter };

@ -0,0 +1,31 @@
/*
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-08-06 16:44:42
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-06 16:44:55
* @FilePath: \General-AI-Platform-Web-Client\src\utils\event\types.ts
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
/*
* @Description:
*/
// 选择模式
export enum SelectMode {
EMPTY = "",
ONE = "one",
MULTI = "multiple"
}
export enum SelectOneType {
EMPTY = "",
GROUP = "group",
POLYGON = "polygon"
}
// 选择事件(用于广播)
export enum SelectEvent {
ONE = "selectOne",
MULTI = "selectMultiple",
CANCEL = "selectCancel"
}

@ -0,0 +1,36 @@
/**
* get localStorage
* @param { String } key
*/
export function getLocal(key: string) {
if (!key) throw new Error("key is empty");
const value = localStorage.getItem(key);
return value ? JSON.parse(value) : null;
}
/**
* set localStorage
* @param { String } key
* @param value
*/
export function setLocal(key: string, value: unknown) {
if (!key) throw new Error("key is empty");
if (!value) return;
return localStorage.setItem(key, JSON.stringify(value));
}
/**
* remove localStorage
* @param { String } key
*/
export function removeLocal(key: string) {
if (!key) throw new Error("key is empty");
return localStorage.removeItem(key);
}
/**
* clear localStorage
*/
export function clearLocal() {
return localStorage.clear();
}

@ -0,0 +1,25 @@
/**
*
* @param edges
* @param radius
* @returns
*/
const getPolygonVertices = (edges: number, radius: number) => {
const vertices = [];
const interiorAngle = (Math.PI * 2) / edges;
let rotationAdjustment = -Math.PI / 2;
if (edges % 2 === 0) {
rotationAdjustment += interiorAngle / 2;
}
for (let i = 0; i < edges; i++) {
// 画圆取顶点坐标
const rad = i * interiorAngle + rotationAdjustment;
vertices.push({
x: Math.cos(rad) * radius,
y: Math.sin(rad) * radius
});
}
return vertices;
};
export { getPolygonVertices };

@ -0,0 +1,136 @@
/*
* @Author:
* @Date: 2022-09-05 22:21:55
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-07 16:29:28
* @Description:
*/
// import FontFaceObserver from "fontfaceobserver";
import { useClipboard, useFileDialog, useBase64 } from "@vueuse/core";
import { message } from "@/utils/message";
interface Font {
type: string;
fontFamily: string;
}
/**
* @description:
* @param {Blob|File} file
* @return {String}
*/
export function getImgStr(file: File | Blob): Promise<FileReader["result"]> {
return useBase64(file).promise.value;
}
/**
* @description: json
* @param {String} str
* @return {Promise}
*/
export function downFontByJSON(str: string) {
// const skipFonts = ["arial", "Microsoft YaHei"];
// const fontFamilies: string[] = JSON.parse(str)
// .objects.filter(
// (item: Font) =>
// // 为text 并且不为包含字体
// // eslint-disable-next-line implicit-arrow-linebreak
// item.type.includes("text") && !skipFonts.includes(item.fontFamily)
// )
// .map((item: Font) => item.fontFamily);
const fontFamiliesAll = [];
// TODO 暂时不使用
// .map(fontName => {
// const font = new FontFaceObserver(fontName);
// return font.load(null, 150000);
// });
return Promise.all(fontFamiliesAll);
}
/**
* @description:
* @param {Object} options accept = '', capture = '', multiple = false
* @return {Promise}
*/
export function selectFiles(options: {
accept?: string;
capture?: string;
multiple?: boolean;
}): Promise<FileList | null> {
return new Promise(resolve => {
const { onChange, open } = useFileDialog(options);
onChange(files => {
resolve(files);
});
open();
});
}
/**
* @description:
* @param {String} str base64
* @return {Promise} element
*/
export function insertImgFile(str: string) {
return new Promise(resolve => {
const imgEl = document.createElement("img");
imgEl.src = str;
// 插入页面
document.body.appendChild(imgEl);
imgEl.onload = () => {
resolve(imgEl);
};
});
}
/**
* Copying text to the clipboard
* @param source Copy source
* @param options Copy options
* @returns Promise that resolves when the text is copied successfully, or rejects when the copy fails.
*/
export const clipboardText = async (
source: string,
options?: Parameters<typeof useClipboard>[0]
) => {
try {
await useClipboard({ source, ...options }).copy();
message("复制成功", { type: "success" });
} catch (error) {
message("复制失败", { type: "error" });
throw error;
}
};
export function fetchArrayByAttrObject(record: Record<string, any>): any[] {
if (!record) {
return [
{
key: "key",
value: "value"
}
];
}
const finalArr: Record<string, any>[] = [];
for (const key in record) {
finalArr.push({
key,
value: record[key]
});
}
return finalArr;
}
export function setAttrObjectByArray(
record: Record<string, any>[]
): Record<string, any> {
if (Array.isArray(record) && record.length && record[0].key != "key1") {
const currObj = new Object();
record.map((item: Record<string, any>) => {
currObj[item?.key || "key"] = item.value;
});
return currObj;
}
return {};
}

@ -0,0 +1,14 @@
.deviceSettingAdd_modal_box {
.el-form-item {
margin-right: 0;
}
.el-form-item__label {
font-weight: 500;
font-size: 14px;
color: #333333;
line-height: 16px;
text-align: center;
font-style: normal;
text-transform: none;
}
}

@ -0,0 +1,156 @@
<!--
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-08-07 14:47:44
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-09 16:25:09
* @FilePath: \General-AI-Platform-Web-Client\src\views\deviceSetting\components\add.vue
* @Description: 新建位置
-->
<script setup lang="ts">
// import type { FormInstance } from "element-plus";
import historyAlarm from "@/assets/history_alarm.png";
import { UploadFilled } from "@element-plus/icons-vue";
import { message } from "@/utils/message";
defineOptions({
name: "DeviceSettingAdd"
});
const formData = ref({
name: "",
file: null as File | null
});
const dialogVisible = ref<boolean>(false);
const fileList = ref([]);
const rules = {
name: [{ required: true, message: "请输入位置名称", trigger: "blur" }],
file: [{ required: true, message: "请上传图片文件", trigger: "change" }]
};
const uploadAction = ""; //
const openDialog = () => {
dialogVisible.value = true;
};
const handleFileChange = (file: any) => {
console.log(file, "handleFileChange");
formData.value = {
...formData.value,
file: "https://img.cgmodel.com/image/2020/1010/big/1537169-1390622992.jpg"
};
};
const beforeUpload = (file: File) => {
const isJPG = file.type === "image/jpeg" || file.type === "image/png";
const isLt500K = file.size / 1024 / 1024 < 0.5;
if (!isJPG) {
ElMessage.error("上传图片只能是 JPG/PNG 格式!");
}
if (!isLt500K) {
ElMessage.error("上传图片大小不能超过 500KB!");
}
return isJPG && isLt500K;
};
const submitForm = () => {
const formRef = form.value;
formRef.validate((valid: boolean) => {
if (valid) {
message("提交成功", { type: "success" });
dialogVisible.value = false;
} else {
message("验证失败", { type: "error" });
return false;
}
});
};
// const resetForm = (formEl: FormInstance | undefined) => {
// if (!formEl) return;
// formEl.resetFields();
// };
defineExpose({
openDialog
});
</script>
<template>
<div>
<el-dialog
top="5vh"
v-model="dialogVisible"
title="新建布点位置"
append-to-body
width="44.445vw"
:style="{
borderRadius: '6px'
}"
>
<template #header="{ titleId, titleClass }">
<div class="flex items-center my-header">
<img :src="historyAlarm" class="w-[26px] h-[26px]" />
<h4 :id="titleId" class="pl-[10px]" :class="titleClass">
新建布点位置
</h4>
</div>
</template>
<div class="deviceSettingAdd_modal_box">
<el-form
ref="formRef"
:rules="rules"
:inline="true"
:model="formData"
class="demo-form-inline"
label-position="top"
>
<el-form-item label="位置名称" class="w-full" prop="name">
<el-input v-model="formData.name" placeholder="请输入" clearable />
</el-form-item>
<el-form-item
label="上传图片(请上传png或jpeg格式图片文件尺寸不超过4096*3112px容量不超过20M)"
class="w-full"
prop="file"
>
<el-upload
class="w-full"
drag
:action="uploadAction"
:on-change="handleFileChange"
:before-upload="beforeUpload"
:file-list="fileList"
:limit="1"
accept="image/*"
>
<div>
<div v-if="!formData?.file">
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
拖拽图片到这里或点此添加 <em>点此添加</em>
</div>
</div>
<el-image
v-else
:src="formData?.file || ''"
:fit="'contain'"
class="w-full"
/>
</div>
</el-upload>
</el-form-item>
</el-form>
</div>
<template v-slot:footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm"></el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<style lang="scss">
@import url("./add.scss");
</style>

@ -0,0 +1,235 @@
<template>
<div class="box" v-if="mixinState.mSelectMode === 'one'">
<!-- 字体属性 -->
<div v-show="textTypeConf.includes(mixinState.mSelectOneType)">
<!-- 字体属性 -->
</div>
<!-- ID属性 -->
<div>
<div class="flex-view">
<div class="flex-item">
<span class="label">{{ $t("attributes.id") }}</span>
<div class="content slider-box">
<input
v-model="baseAttr.id"
@change="changeCommon('id', baseAttr.id)"
/>
</div>
</div>
</div>
</div>
<!-- 用户属性 -->
<!-- TODO 缺少删除 图标 -->
<div>
<ul>
<li class="flex-view" v-for="(v, k) in baseAttr.userProperty" :key="k">
<div class="flex-item">
<span class="content slider-box">
<input
v-model="baseAttr.userProperty[k].key"
@change="
changeCommon('userProperty_key', baseAttr.userProperty)
"
/>
</span>
<div class="content slider-box">
<input
v-model="baseAttr.userProperty[k].value"
@change="
changeCommon('userProperty_value', baseAttr.userProperty)
"
/>
</div>
</div>
</li>
<li
class="flex-view"
style="justify-content: center; color: dodgerblue; text-align: center"
@click="
() => {
baseAttr.userProperty.push({
key: 'key' + baseAttr.userProperty.length,
value: 'value'
});
}
"
>
<span>新增一项</span>
</li>
</ul>
</div>
</div>
</template>
<script setup name="AttrBute">
import useSelect from "@/hooks/select";
import { fetchArrayByAttrObject, setAttrObjectByArray } from "@/utils/utils";
//
import { textTypeConf } from "@/config/attribute/baseType";
const event = inject("event");
const update = getCurrentInstance();
const { mixinState, canvasEditor } = useSelect();
//
const baseAttr = reactive({
id: "",
opacity: 0,
angle: 0,
fill: "#fff",
left: 0,
top: 0,
strokeWidth: 0,
strokeDashArray: [],
stroke: "#fff",
shadow: {
color: "#fff",
blur: 0,
offsetX: 0,
offsetY: 0
},
points: {},
selectable: false,
userProperty: [
{
key: "key",
value: "value"
}
]
});
//
const animationAttr = reactive({
type: "None"
});
//
const fontAttr = reactive({
fontSize: 0,
fontFamily: "",
lineHeight: 0,
charSpacing: 0,
fontWeight: "",
textBackgroundColor: "#fff",
textAlign: "",
fontStyle: "",
underline: false,
linethrough: false,
overline: false
});
const getObjectAttr = e => {
const activeObject = canvasEditor.canvas.getActiveObject();
// obj
if (e && e.target && e.target !== activeObject) return;
if (activeObject) {
// base
baseAttr.id = activeObject.get("id");
baseAttr.userProperty = fetchArrayByAttrObject(
activeObject.get("userProperty")
);
baseAttr.opacity = activeObject.get("opacity") * 100;
baseAttr.fill = activeObject.get("fill");
baseAttr.left = activeObject.get("left");
baseAttr.top = activeObject.get("top");
baseAttr.stroke = activeObject.get("stroke");
baseAttr.strokeWidth = activeObject.get("strokeWidth");
baseAttr.shadow = activeObject.get("shadow") || {};
baseAttr.angle = activeObject.get("angle") || 0;
baseAttr.points = activeObject.get("points") || {};
baseAttr.selectable = activeObject.get("selectable");
console.log("activeObject", activeObject);
const textTypes = ["i-text", "text", "textbox"];
if (textTypes.includes(activeObject.type)) {
fontAttr.fontSize = activeObject.get("fontSize");
fontAttr.fontFamily = activeObject.get("fontFamily");
fontAttr.lineHeight = activeObject.get("lineHeight");
fontAttr.textAlign = activeObject.get("textAlign");
fontAttr.underline = activeObject.get("underline");
fontAttr.linethrough = activeObject.get("linethrough");
fontAttr.charSpacing = activeObject.get("charSpacing");
fontAttr.overline = activeObject.get("overline");
fontAttr.fontStyle = activeObject.get("fontStyle");
fontAttr.textBackgroundColor = activeObject.get("textBackgroundColor");
fontAttr.fontWeight = activeObject.get("fontWeight");
}
// update
if (activeObject?.animation && activeObject?.animation?.type != "None") {
const animateObject = activeObject.get("animation");
animationAttr.type = animateObject.type;
}
}
};
const selectCancel = () => {
baseAttr.fill = "";
update?.proxy?.$forceUpdate();
};
const init = () => {
//
event.on("selectCancel", selectCancel);
event.on("selectOne", getObjectAttr);
canvasEditor.canvas.on("object:modified", getObjectAttr);
};
//
const changeCommon = (key, value) => {
const activeObject = canvasEditor.canvas.getActiveObjects()[0];
//
if (key === "opacity") {
activeObject && activeObject.set(key, value / 100);
canvasEditor.canvas.renderAll();
return;
}
//
if (key === "angle") {
activeObject.rotate(value);
canvasEditor.canvas.renderAll();
return;
}
//
if (key === "userProperty_key") {
activeObject &&
activeObject.set(
"userProperty",
setAttrObjectByArray(baseAttr.userProperty)
);
canvasEditor.canvas.renderAll();
return;
}
if (key === "userProperty_value") {
activeObject &&
activeObject.set(
"userProperty",
setAttrObjectByArray(baseAttr.userProperty)
);
canvasEditor.canvas.renderAll();
return;
}
activeObject && activeObject.set(key, value);
canvasEditor.canvas.renderAll();
//
getObjectAttr();
};
onMounted(init);
onBeforeUnmount(() => {
event.off("selectCancel", selectCancel);
event.off("selectOne", getObjectAttr);
canvasEditor.canvas.off("object:modified", getObjectAttr);
});
</script>
<style scoped>
.flex-view {
width: 100%;
margin-bottom: 5px;
padding: 5px;
display: inline-flex;
justify-content: space-between;
border-radius: 5px;
background: #f6f7f9;
}
</style>

@ -0,0 +1,316 @@
<!--
* @Description: 设备选择
-->
<template>
<div v-if="!mixinState.mSelectMode">
<div class="content">
<div>
<span>本地模型</span>
<ul>
<li
v-for="(info, i) in locaWatchList"
:key="`${i}-logo1-button`"
:draggable="true"
@click="addItem(info)"
@dragend="event => dragItem(event, info)"
>
<span>
{{ info.name }}
</span>
</li>
</ul>
</div>
</div>
</div>
</template>
<script setup name="CanvasSize" lang="ts">
// import { Modal } from "view-ui-plus";
import useSelect from "@/hooks/select";
// import { cloneDeep } from "lodash-es";
import { v4 as uuid } from "uuid";
// import { useI18n } from "vue-i18n";
import { useWatchModels } from "../hooks/useWatchModels";
import { useDeviceObject } from "../hooks/useDeviceObject";
// import watchOnlineSelected from "../../../assets/modelSetting/watchOnlineSelected.svg";
// const testSrc =
// "https://img.cgmodel.com/image/2020/1010/big/1537169-1390622992.jpg";
const { fabric, mixinState, canvasEditor } = useSelect();
const { locaWatchList } = useWatchModels();
const { initDeviceGroupObjects } = useDeviceObject();
// const { t } = useI18n();
const defaultPosition = {
left: 100,
top: 100,
shadow: "",
fontFamily: "1-1"
};
interface materialTypeI {
value: string;
label: string;
list?: materialItemI[];
}
interface materialItemI {
value: string;
label: string;
tempUrl: string;
src: string;
}
// const allType: materialTypeI = {
// value: "",
// label: ""
// };
const state = reactive({
search: "",
// placeholder: <undefined | string>"",
jsonFile: null,
materialType: [""], //
materialTypelist: [], //
materialist: [] //
});
//
canvasEditor.getMaterialType("svg").then((list: materialTypeI[]) => {
state.materialTypelist = [...list];
state.materialist = list;
});
//
// const handleChange = (e, item) => {
// //
// const { label, value } = item[0];
// state.placeholder = label;
// state.search = "";
// filterTypeList(value);
// };
//
// const filterTypeList = (value: string) => {
// //
// if (!value) {
// state.materialist = cloneDeep(state.materialTypelist);
// } else {
// //
// const materialTypeInfoList =
// state.materialTypelist.filter(item => item.value === value) || [];
// state.materialist = materialTypeInfoList;
// }
// //
// if (state.search) {
// const list = cloneDeep(state.materialist);
// //
// state.materialist = list.map(item => {
// if (item.list) {
// item.list = item.list.filter(info => info.label.includes(state.search));
// }
// return item;
// });
// }
// };
// const search = () => {
// const [typeValue] = state.materialType;
// filterTypeList(typeValue);
// };
const dragItem = (event, deviceItem) => {
console.log(event, deviceItem, "dragItem");
fabric.util.enlivenObjects(
[
{
...defaultPosition,
shadow: "",
fontFamily: "arial",
id: uuid(),
name: "svg元素",
...initDeviceGroupObjects(deviceItem)
}
],
function (objects) {
// objects JSONGroup
const item = objects[0];
// if (!option) {
// groupText.center();
// }
// GroupCanvas
// var canvas = new fabric.Canvas('canvas-id');
// canvas.add(group);
canvasEditor.dragAddItem(event, item);
// Canvas
// canvas.renderAll();
}
);
};
//
const addItem = deviceItem => {
// fabric.util.enlivenObjects(deviceItem.groupObject?.objects, enlivenedObjects => {
// const item = new fabric.Group(enlivenedObjects, {
// // ...options,
// ...defaultPosition,
// shadow: "",
// fontFamily: "arial",
// id: uuid(),
// name: ""
// });
// canvasEditor.canvas.add(item);
// canvasEditor.canvas.setActiveObject(item);
// canvasEditor.canvas.requestRenderAll();
// });
fabric.util.enlivenObjects(
[
{
...defaultPosition,
id: uuid(),
// name: "svg",
...initDeviceGroupObjects(deviceItem)
}
],
function (objects) {
// objects JSONGroup
const item = objects[0];
// if (!option) {
// groupText.center();
// }
// GroupCanvas
// var canvas = new fabric.Canvas('canvas-id');
// canvas.add(group);
canvasEditor.canvas.add(item);
canvasEditor.canvas.setActiveObject(item);
canvasEditor.canvas.requestRenderAll();
// Canvas
// canvas.renderAll();
}
);
// console.log(e, "addItem_e");
// const url = watchOnlineSelected;
// fabric.loadSVGFromURL(url, (objects, options) => {
// const item = fabric.util.groupSVGElements(objects, {
// ...options,
// ...defaultPosition,
// id: uuid(),
// name: "svg"
// });
// });
};
// const DefaultSize = {
// width: 1200,
// height: 900
// };
// const modalData = reactive({
// width: DefaultSize.width,
// height: DefaultSize.height
// });
// let width = ref(DefaultSize.width);
// let height = ref(DefaultSize.height);
// let presetSize = reactive([
// {
// label: t("red_book_vertical"),
// width: 900,
// height: 1200
// },
// {
// label: t("red_book_horizontal"),
// width: 1200,
// height: 900
// },
// {
// label: t("phone_wallpaper"),
// width: 1080,
// height: 1920
// },
// {
// label: "kindle",
// width: 1200,
// height: 860
// },
// {
// label: "kindle-resize",
// width: 860,
// height: 1200
// }
// ]);
const DefaultSize = {
width: 1200,
height: 900
};
onMounted(() => {
canvasEditor.setSize(DefaultSize.width, DefaultSize.height);
canvasEditor.on("sizeChange", (width, height) => {
width.value = width;
height.value = height;
});
// canvas.editor.editorWorkspace.setSize(width.value, height.value);
// canvas.editor.editorWorkspace = new EditorWorkspace(canvas.c, {
// width: width.value,
// height: height.value,
// });
});
// const setSizeBy = (w, h) => {
// modalData.width = w;
// modalData.height = h;
// };
// const setSize = () => {
// canvasEditor.setSize(width.value, height.value);
// // canvas.editor.editorWorkspace.setSize(width.value, height.value);
// };
// const handleClose = () => {
// showModal.value = false;
// };
// const handleConfirm = () => {
// width.value = modalData.width;
// height.value = modalData.height;
// setSize();
// handleClose();
// };
</script>
<style scoped lang="scss">
.search-box {
padding-top: 10px;
display: flex;
.input {
margin-left: 10px;
}
}
.tmpl-img {
display: inline-block;
width: 53px;
margin-left: 2px;
margin-bottom: 2px;
background: #f5f5f5;
padding: 6px;
cursor: pointer;
// width: 135px;
// cursor: pointer;
// margin-right: 5px;
}
.form-wrap {
display: flex;
justify-content: space-around;
align-content: center;
margin-bottom: 10px;
}
</style>
../hooks/useWatchModels../hooks/useDeviceObject

@ -0,0 +1,581 @@
/**
* @
* 1. 使
*/
export const useDeviceObject = () => {
const initDeviceGroupObjects: Record<string, any> = (record: {
id: string;
value: string;
}) => {
console.log(record, "initDeviceGroupObjects");
// const { value } = record;
// let watchIconObject = watchIcon2;
// switch (value) {
// case "watchError":
// watchIconObject = watchIcon1;
// break;
// case "watchOnline":
// watchIconObject = watchIcon2;
// break;
// case "watchOutline":
// watchIconObject = watchIcon3;
// break;
// case "watchWarn":
// default:
// watchIconObject = watchIcon4;
// break;
// }
return {
selectable: true,
hasControls: true,
lockUniScaling: true, // 当设置为trueObject将无法被锁定比例进行缩放。默认值为false。
lockScalingX: true, // 当设置为trueObject水平方向将无法被缩放。默认值为false。
lockScalingY: true, // 当设置为trueObject垂直方向将无法被缩放。默认值为false。
lockRotation: true, // 当设置为trueObject的旋转将被锁定。默认值为false。
type: "group",
version: "5.3.0",
originX: "left",
originY: "top",
left: 20.9866,
top: 16.4716,
width: 131,
height: 66,
fill: "rgb(0,0,0)",
stroke: null,
strokeWidth: 0,
strokeDashArray: null,
strokeLineCap: "butt",
strokeDashOffset: 0,
strokeLineJoin: "miter",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
objects: [
{
type: "group",
version: "5.3.0",
originX: "left",
originY: "top",
left: -65.5,
top: -33,
width: 131,
height: 66,
fill: "rgb(0,0,0)",
stroke: null,
strokeWidth: 0,
strokeDashArray: null,
strokeLineCap: "butt",
strokeDashOffset: 0,
strokeLineJoin: "miter",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: "",
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
selectable: true,
hasControls: true,
objects: [
{
type: "path",
version: "5.3.0",
originX: "left",
originY: "top",
left: -60,
top: -31.5,
width: 119,
height: 54,
fill: "white",
stroke: null,
strokeWidth: 1,
strokeDashArray: null,
strokeLineCap: "butt",
strokeDashOffset: 0,
strokeLineJoin: "miter",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
backgroundColor: "",
fillRule: "evenodd",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
selectable: true,
hasControls: true,
path: [
["M", 10, 2],
["C", 7.79086, 2, 6, 3.79086, 6, 6],
["L", 6, 44],
["C", 6, 46.2091, 7.79086, 48, 10, 48],
["L", 58.8, 48],
["L", 66, 56],
["L", 73.2, 48],
["L", 121, 48],
["C", 123.209, 48, 125, 46.2091, 125, 44],
["L", 125, 6],
["C", 125, 3.79086, 123.209, 2, 121, 2],
["L", 10, 2],
["Z"]
]
},
{
type: "path",
version: "5.3.0",
originX: "left",
originY: "top",
left: -61,
top: -32.5,
width: 121,
height: 56.4948,
fill: "rgba(21,77,221,0.2)",
stroke: null,
strokeWidth: 1,
strokeDashArray: null,
strokeLineCap: "butt",
strokeDashOffset: 0,
strokeLineJoin: "miter",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
selectable: true,
hasControls: true,
path: [
["M", 58.8, 48],
["L", 59.5433, 47.331],
["L", 59.2454, 47],
["L", 58.8, 47],
["L", 58.8, 48],
["Z"],
["M", 66, 56],
["L", 65.2567, 56.669],
["L", 66, 57.4948],
["L", 66.7433, 56.669],
["L", 66, 56],
["Z"],
["M", 73.2, 48],
["L", 73.2, 47],
["L", 72.7546, 47],
["L", 72.4567, 47.331],
["L", 73.2, 48],
["Z"],
["M", 7, 6],
["C", 7, 4.34315, 8.34315, 3, 10, 3],
["L", 10, 1],
["C", 7.23858, 1, 5, 3.23857, 5, 6],
["L", 7, 6],
["Z"],
["M", 7, 44],
["L", 7, 6],
["L", 5, 6],
["L", 5, 44],
["L", 7, 44],
["Z"],
["M", 10, 47],
["C", 8.34315, 47, 7, 45.6569, 7, 44],
["L", 5, 44],
["C", 5, 46.7614, 7.23858, 49, 10, 49],
["L", 10, 47],
["Z"],
["M", 58.8, 47],
["L", 10, 47],
["L", 10, 49],
["L", 58.8, 49],
["L", 58.8, 47],
["Z"],
["M", 58.0567, 48.669],
["L", 65.2567, 56.669],
["L", 66.7433, 55.331],
["L", 59.5433, 47.331],
["L", 58.0567, 48.669],
["Z"],
["M", 66.7433, 56.669],
["L", 73.9433, 48.669],
["L", 72.4567, 47.331],
["L", 65.2567, 55.331],
["L", 66.7433, 56.669],
["Z"],
["M", 121, 47],
["L", 73.2, 47],
["L", 73.2, 49],
["L", 121, 49],
["L", 121, 47],
["Z"],
["M", 124, 44],
["C", 124, 45.6569, 122.657, 47, 121, 47],
["L", 121, 49],
["C", 123.761, 49, 126, 46.7614, 126, 44],
["L", 124, 44],
["Z"],
["M", 124, 6],
["L", 124, 44],
["L", 126, 44],
["L", 126, 6],
["L", 124, 6],
["Z"],
["M", 121, 3],
["C", 122.657, 3, 124, 4.34315, 124, 6],
["L", 126, 6],
["C", 126, 3.23858, 123.761, 1, 121, 1],
["L", 121, 3],
["Z"],
["M", 10, 3],
["L", 121, 3],
["L", 121, 1],
["L", 10, 1],
["L", 10, 3],
["Z"]
]
},
{
type: "rect",
version: "5.3.0",
originX: "left",
originY: "top",
left: -52,
top: -24.5,
width: 32,
height: 32,
fill: "#52C41A",
stroke: null,
strokeWidth: 1,
strokeDashArray: null,
strokeLineCap: "butt",
strokeDashOffset: 0,
strokeLineJoin: "miter",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
rx: 2,
ry: 2,
selectable: true,
hasControls: true
},
{
type: "path",
version: "5.3.0",
originX: "left",
originY: "top",
left: -45.9945,
top: -17.6,
width: 19.8963,
height: 0,
fill: "",
stroke: "white",
strokeWidth: 1.2,
strokeDashArray: null,
strokeLineCap: "round",
strokeDashOffset: 0,
strokeLineJoin: "round",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
selectable: true,
hasControls: true,
path: [
["M", 40.0018, 16],
["L", 29.5301, 16],
["L", 20.1055, 16]
]
},
{
type: "path",
version: "5.3.0",
originX: "left",
originY: "top",
left: -36.5727,
top: -17.6,
width: 0,
height: 8.8419,
fill: "",
stroke: "white",
strokeWidth: 1.2,
strokeDashArray: null,
strokeLineCap: "round",
strokeDashOffset: 0,
strokeLineJoin: "round",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
selectable: true,
hasControls: true,
path: [
["M", 29.5273, 24.8419],
["L", 29.5273, 16]
]
},
{
type: "path",
version: "5.3.0",
originX: "left",
originY: "top",
left: -46.1,
top: -10.5375,
width: 18.5869,
height: 10.9372,
fill: "white",
stroke: "white",
strokeWidth: 1.2,
strokeDashArray: null,
strokeLineCap: "round",
strokeDashOffset: 0,
strokeLineJoin: "round",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
selectable: true,
hasControls: true,
path: [
["M", 21.8972, 23.0625],
["L", 38.5869, 27.5048],
["L", 37.6746, 28.8773],
["L", 35.579, 32.6272],
["L", 34.6667, 33.9997],
["L", 20, 30.0959],
["L", 21.8972, 23.0625],
["Z"]
]
},
{
type: "path",
version: "5.3.0",
originX: "left",
originY: "top",
left: -30.5219,
top: -4.7211,
width: 4.1186,
height: 4.5576,
fill: "",
stroke: "white",
strokeWidth: 1.2,
strokeDashArray: null,
strokeLineCap: "round",
strokeDashOffset: 0,
strokeLineJoin: "round",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
selectable: true,
hasControls: true,
path: [
["M", 37.6737, 28.8789],
["L", 39.6967, 29.4174],
["L", 38.6126, 33.4365],
["L", 35.5781, 32.6288]
]
},
{
type: "path",
version: "5.3.0",
originX: "left",
originY: "top",
left: -43.3008,
top: -7.6387,
width: 4.2123,
height: 4.181,
fill: "",
stroke: "#52C41A",
strokeWidth: 0.5,
strokeDashArray: null,
strokeLineCap: "butt",
strokeDashOffset: 0,
strokeLineJoin: "miter",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: null,
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
selectable: true,
hasControls: true,
path: [
["M", 26.6615, 27.7018],
["C", 26.6615, 28.8548, 25.7201, 29.7923, 24.5554, 29.7923],
["C", 23.3906, 29.7923, 22.4492, 28.8548, 22.4492, 27.7018],
["C", 22.4492, 26.5488, 23.3906, 25.6113, 24.5554, 25.6113],
["C", 25.7201, 25.6113, 26.6615, 26.5488, 26.6615, 27.7018],
["Z"]
]
}
]
},
{
type: "textbox",
version: "5.3.0",
originX: "left",
originY: "top",
left: -15.709,
top: -15.0177,
width: 67.6873,
height: 13.56,
fill: "rgb(0,0,0)",
stroke: null,
strokeWidth: 1,
strokeDashArray: null,
strokeLineCap: "butt",
strokeDashOffset: 0,
strokeLineJoin: "miter",
strokeUniform: false,
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
shadow: "",
visible: true,
backgroundColor: "",
fillRule: "nonzero",
paintFirst: "fill",
globalCompositeOperation: "source-over",
skewX: 0,
skewY: 0,
fontFamily: "arial",
fontWeight: "normal",
fontSize: 12,
text: record?.name || "设备",
underline: false,
overline: false,
linethrough: false,
textAlign: "left",
fontStyle: "normal",
lineHeight: 1.16,
textBackgroundColor: "",
charSpacing: 0,
styles: [],
direction: "ltr",
path: null,
pathStartOffset: 0,
pathSide: "left",
pathAlign: "baseline",
minWidth: 20,
splitByGrapheme: true,
selectable: true,
hasControls: true
}
]
};
};
return {
initDeviceGroupObjects
};
};

@ -0,0 +1,118 @@
/*
* @Author: zhoux zhouxia@supervision.ltd
* @Date: 2023-12-13 14:29:17
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-08-07 11:31:52
* @FilePath: \vue-fabric-editor\src\hooks\useAddModels.ts
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
// import {watch00} from '@/assets/modelSettingJsons/watch01'
import watchError from "@/assets/modelSetting/watchError.svg";
import watchOnline from "@/assets/modelSetting/watchOnline.svg";
import watchOutline from "@/assets/modelSetting/watchOutline.svg";
import watchWarn from "@/assets/modelSetting/watchWarn.svg";
import watchErrorSelected from "@/assets/modelSetting/watchErrorSelected.svg";
import watchOnlineSelected from "@/assets/modelSetting/watchOnlineSelected.svg";
import watchOutlineSelected from "@/assets/modelSetting/watchOutlineSelected.svg";
import watchWarnSelected from "@/assets/modelSetting/watchWarnSelected.svg";
export interface materialItemI {
value: string;
name: string;
tempUrl: string;
src: string;
color?: string;
}
const customObjectJson = {
type: "group",
objects: [
{
type: "rect",
left: 100,
top: 100,
width: 100,
height: 100,
fill: "red"
},
{
type: "circle",
left: 200,
top: 100,
radius: 50,
fill: "blue"
},
{
type: "text",
left: 150,
top: 200,
text: "Hello",
fontSize: 24,
fill: "white"
}
]
};
export const useWatchModels = () => {
const locaWatchList: materialItemI[] = [
{
value: "watchError",
name: "摄像头1",
tempUrl: "",
groupObject: customObjectJson,
src: watchError,
color: "#E80D0D"
},
{
value: "watchOnline",
name: "摄像头",
tempUrl: "",
src: watchOnline,
color: "#52C41A"
},
{
value: "watchOutline",
name: "摄像头",
tempUrl: "",
src: watchOutline,
color: "#CCCCCC"
},
{
value: "watchWarn",
name: "摄像头",
tempUrl: "",
src: watchWarn,
color: "#FAAD14"
},
{
value: "watchErrorSelected",
name: "摄像头",
tempUrl: "",
src: watchErrorSelected,
color: "#E80D0D"
},
{
value: "watchOnlineSelected",
name: "摄像头",
tempUrl: "",
src: watchOnlineSelected,
color: "#52C41A"
},
{
value: "watchOutlineSelected",
name: "摄像头",
tempUrl: "",
src: watchOutlineSelected,
color: "#CCCCCC"
},
{
value: "watchWarnSelected",
name: "摄像头",
tempUrl: "",
src: watchWarnSelected,
color: "#FAAD14"
}
];
return {
locaWatchList
};
};

@ -0,0 +1,53 @@
.deviceSetting_wrap {
height: 100vh;
background: #fff;
border-radius: 2px;
& > header {
box-sizing: border-box;
padding: 16px;
height: 62px;
font-family: Douyin Sans, Douyin Sans;
font-weight: bold;
font-size: 20px;
color: #333333;
border-bottom: 1px solid rgba(21, 77, 221, 0.2);
}
.main_content {
/* background: red; */
height: calc(100vh - 160px);
.device_add_wrap {
width: 57.64vw;
margin: 0 auto;
padding-top: 40px;
.bg_preview {
height: 412px;
background-color: red;
}
}
}
/* TODO 待使用 */
.right-bar {
width: 304px;
height: 100%;
padding: 10px;
overflow-y: auto;
background: #fff;
}
#workspace {
flex: 1;
width: 100%;
position: relative;
overflow: hidden;
}
.content {
flex: 1;
width: 220px;
padding: 10px;
padding-top: 0;
height: 100%;
overflow-y: auto;
}
}

@ -1,70 +1,206 @@
<script setup lang="ts">
import { useRouter } from "vue-router";
import noExist from "@/assets/status/404.svg?component";
defineOptions({
name: "暂未开放"
});
const router = useRouter();
</script>
<template>
<div class="flex justify-center items-center h-[640px]">
<noExist />
<div class="ml-12">
<p
class="mb-4 text-4xl font-medium dark:text-white"
v-motion
:initial="{
opacity: 0,
y: 100
}"
:enter="{
opacity: 1,
y: 0,
transition: {
delay: 100
}
}"
>
暂未开放
</p>
<p
class="mb-4 text-gray-500"
v-motion
:initial="{
opacity: 0,
y: 100
}"
:enter="{
opacity: 1,
y: 0,
transition: {
delay: 300
}
}"
<div class="main_booy_container deviceSetting_wrap">
<header class="flex items-center justify-between" v-if="state.showFabric">
<h3 class="computePowerAllocation_header">设备布点</h3>
<div>
<el-button type="primary" @click="addLocal" v-if="localList.length"
>新建位置</el-button
>
抱歉你访问的页面暂未开放
</div>
<!-- 预览 -->
<!-- <previewCurrent /> -->
<!-- <save></save> -->
<!-- <waterMark /> -->
</header>
<div class="main_content">
<div class="device_add_wrap" v-show="!localList.length">
<div class="bg_preview">
<!-- -->
</div>
<p class="pt-[16px] pb-[24px] pf-1 leading-[16px]">
本页面为数据可视化大屏中设备点位界面的配置页通过上传设备所处区域的实拍图或平面图后根据设备的实际位置简单的使用拖拽等操作将设备列表中的设备与位置进行匹配使之能够在数据可视化大屏中更加精细化而直观的了解设备的具体位置与状态
</p>
<el-button
type="primary"
@click="router.push('/')"
v-motion
:initial="{
opacity: 0,
y: 100
}"
:enter="{
opacity: 1,
y: 0,
transition: {
delay: 500
}
}"
<div class="flex justify-center">
<el-button type="primary" @click="addLocal"></el-button>
</div>
</div>
<div
class="flex w-full h-full device_select_wrap"
v-show="localList.length"
>
返回首页
</el-button>
<!-- <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane label="User" name="first"></el-tab-pane>
</el-tabs> -->
<!-- 左侧画布区域 -->
<div id="workspace" class="h-full">
<div class="canvas-box">
<!-- <div class="inside-shadow"></div> -->
<canvas
id="canvas"
:class="state.ruler ? 'design-stage-grid' : ''"
/>
<!-- <dragMode v-if="state.showFabric"></dragMode>
<zoom></zoom> -->
<!-- <mouseMenu></mouseMenu> -->
</div>
</div>
<!-- 右侧属性区域-->
<div class="right-bar" v-if="state.showFabric">
<DeviceSelect />
<DeviceAttr />
</div>
</div>
</div>
<DeviceSettingAdd ref="deviceSettingAddRef" />
</div>
</template>
<script name="Home" setup>
//
import DeviceAttr from "./components/deviceAttr.vue";
import DeviceSettingAdd from "./components/add.vue";
import DeviceSelect from "./components/deviceSelect.vue";
//
import jsonFile1 from "./testData/bg01.json";
// TODO
//
import { CanvasEventEmitter } from "@/utils/event/notifier";
// import { downFile } from '@/utils/utils';
import { fabric } from "fabric";
import Editor, {
DringPlugin,
AlignGuidLinePlugin,
ControlsPlugin,
ControlsRotatePlugin,
CenterAlignPlugin,
LayerPlugin,
CopyPlugin,
MoveHotKeyPlugin,
DeleteHotKeyPlugin,
GroupPlugin,
DrawLinePlugin,
GroupTextEditorPlugin,
GroupAlignPlugin,
WorkspacePlugin,
DownFontPlugin,
HistoryPlugin,
FlipPlugin,
RulerPlugin,
MaterialPlugin
} from "@/core";
//
const canvasEditor = new Editor();
const event = new CanvasEventEmitter();
const state = reactive({
menuActive: 2,
showFabric: false,
toolsActive: 0,
attrBarShow: true,
select: null,
ruler: false
});
// &
const localList = ref([]);
const deviceSettingAddRef = ref("");
//
const initFile = () => {
console.log("canvasEditor");
canvasEditor.insertSvgFile(jsonFile1);
};
const initFabric = () => {
// fabric
const canvas = new fabric.Canvas("canvas", {
fireRightClick: true, // button3
stopContextMenu: true, //
controlsAboveOverlay: true // clipPath
});
canvas.loadFromJSON(jsonFile1, canvas.renderAll.bind(canvas));
//
canvasEditor.init(canvas);
canvasEditor.use(DringPlugin);
canvasEditor.use(AlignGuidLinePlugin);
canvasEditor.use(ControlsPlugin);
canvasEditor.use(ControlsRotatePlugin);
canvasEditor.use(CenterAlignPlugin);
canvasEditor.use(LayerPlugin);
canvasEditor.use(CopyPlugin);
canvasEditor.use(MoveHotKeyPlugin);
canvasEditor.use(DeleteHotKeyPlugin);
canvasEditor.use(GroupPlugin);
canvasEditor.use(DrawLinePlugin);
canvasEditor.use(GroupTextEditorPlugin);
canvasEditor.use(GroupAlignPlugin);
canvasEditor.use(WorkspacePlugin);
canvasEditor.use(DownFontPlugin);
canvasEditor.use(HistoryPlugin);
canvasEditor.use(FlipPlugin);
canvasEditor.use(RulerPlugin);
canvasEditor.use(MaterialPlugin);
event.init(canvas);
state.showFabric = true;
nextTick(() => {
initFile();
});
};
/**
* @设备点位
*/
function addLocal() {
deviceSettingAddRef.value?.openDialog();
}
function fetchLocalList() {
localList.value = [
// {
// id: "1",
// name: "1",
// type: "1",
// x: 100,
// y: 100,
// width: 100,
// height: 100,
// color: "#ff0000",
// text: "1",
// textX: 100,
// textY: 100,
// textFont: "20px Arial",
// textFill: "#000000",
// textStroke: "#ffffff",
// textStrokeWidth: 1,
// textBackgroundColor: "#ffffff"
// }
];
}
//
watch(
() => localList.value,
() => {
console.log("localList", localList);
if (localList.value.length) {
nextTick(() => {
initFabric();
});
}
}
);
onMounted(() => {
fetchLocalList();
});
provide("fabric", fabric);
provide("event", event);
provide("canvasEditor", canvasEditor);
</script>
<style lang="scss" scoped>
@import url("./index.scss");
</style>

@ -0,0 +1,85 @@
{
"version": "5.3.0",
"objects": [
{
"type": "rect",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 0,
"top": 0,
"width": 1200,
"height": 900,
"fill": "rgba(255,255,255,1)",
"stroke": null,
"strokeWidth": 0,
"strokeDashArray": null,
"strokeLineCap": "butt",
"strokeDashOffset": 0,
"strokeLineJoin": "miter",
"strokeUniform": false,
"strokeMiterLimit": 4,
"scaleX": 1,
"scaleY": 1,
"angle": 0,
"flipX": false,
"flipY": false,
"opacity": 1,
"shadow": null,
"visible": true,
"backgroundColor": "",
"fillRule": "nonzero",
"paintFirst": "fill",
"globalCompositeOperation": "source-over",
"skewX": 0,
"skewY": 0,
"rx": 0,
"ry": 0,
"id": "workspace",
"selectable": false,
"hasControls": false
},
{
"type": "image",
"version": "5.3.0",
"originX": "left",
"originY": "top",
"left": 23.2346,
"top": 23.0507,
"width": 1600,
"height": 1200,
"fill": "rgb(0,0,0)",
"stroke": null,
"strokeWidth": 0,
"strokeDashArray": null,
"strokeLineCap": "butt",
"strokeDashOffset": 0,
"strokeLineJoin": "miter",
"strokeUniform": false,
"strokeMiterLimit": 4,
"scaleX": 0.721,
"scaleY": 0.721,
"angle": 0,
"flipX": false,
"flipY": false,
"opacity": 1,
"shadow": null,
"visible": true,
"backgroundColor": "",
"fillRule": "nonzero",
"paintFirst": "fill",
"globalCompositeOperation": "source-over",
"skewX": 0,
"skewY": 0,
"cropX": 0,
"cropY": 0,
"id": "a3ab29c6-7008-49fe-abf3-edc9a47cd460",
"selectable": false,
"hasControls": false,
"evented": false,
"crossOrigin": null,
"src": "https://img.cgmodel.com/image/2020/1010/big/1537169-1390622992.jpg",
"filters": []
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

@ -0,0 +1,78 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
const EffectScope: typeof import("vue")["EffectScope"];
const computed: typeof import("vue")["computed"];
const createApp: typeof import("vue")["createApp"];
const customRef: typeof import("vue")["customRef"];
const defineAsyncComponent: typeof import("vue")["defineAsyncComponent"];
const defineComponent: typeof import("vue")["defineComponent"];
const effectScope: typeof import("vue")["effectScope"];
const getCurrentInstance: typeof import("vue")["getCurrentInstance"];
const getCurrentScope: typeof import("vue")["getCurrentScope"];
const h: typeof import("vue")["h"];
const inject: typeof import("vue")["inject"];
const isProxy: typeof import("vue")["isProxy"];
const isReactive: typeof import("vue")["isReactive"];
const isReadonly: typeof import("vue")["isReadonly"];
const isRef: typeof import("vue")["isRef"];
const markRaw: typeof import("vue")["markRaw"];
const nextTick: typeof import("vue")["nextTick"];
const onActivated: typeof import("vue")["onActivated"];
const onBeforeMount: typeof import("vue")["onBeforeMount"];
const onBeforeUnmount: typeof import("vue")["onBeforeUnmount"];
const onBeforeUpdate: typeof import("vue")["onBeforeUpdate"];
const onDeactivated: typeof import("vue")["onDeactivated"];
const onErrorCaptured: typeof import("vue")["onErrorCaptured"];
const onMounted: typeof import("vue")["onMounted"];
const onRenderTracked: typeof import("vue")["onRenderTracked"];
const onRenderTriggered: typeof import("vue")["onRenderTriggered"];
const onScopeDispose: typeof import("vue")["onScopeDispose"];
const onServerPrefetch: typeof import("vue")["onServerPrefetch"];
const onUnmounted: typeof import("vue")["onUnmounted"];
const onUpdated: typeof import("vue")["onUpdated"];
const provide: typeof import("vue")["provide"];
const reactive: typeof import("vue")["reactive"];
const readonly: typeof import("vue")["readonly"];
const ref: typeof import("vue")["ref"];
const resolveComponent: typeof import("vue")["resolveComponent"];
const shallowReactive: typeof import("vue")["shallowReactive"];
const shallowReadonly: typeof import("vue")["shallowReadonly"];
const shallowRef: typeof import("vue")["shallowRef"];
const toRaw: typeof import("vue")["toRaw"];
const toRef: typeof import("vue")["toRef"];
const toRefs: typeof import("vue")["toRefs"];
const toValue: typeof import("vue")["toValue"];
const triggerRef: typeof import("vue")["triggerRef"];
const unref: typeof import("vue")["unref"];
const useAttrs: typeof import("vue")["useAttrs"];
const useCssModule: typeof import("vue")["useCssModule"];
const useCssVars: typeof import("vue")["useCssVars"];
const useSlots: typeof import("vue")["useSlots"];
const watch: typeof import("vue")["watch"];
const watchEffect: typeof import("vue")["watchEffect"];
const watchPostEffect: typeof import("vue")["watchPostEffect"];
const watchSyncEffect: typeof import("vue")["watchSyncEffect"];
}
// for type re-export
declare global {
// @ts-ignore
export type {
Component,
ComponentPublicInstance,
ComputedRef,
ExtractDefaultPropTypes,
ExtractPropTypes,
ExtractPublicPropTypes,
InjectionKey,
PropType,
Ref,
VNode,
WritableComputedRef
} from "vue";
import("vue");
}

45
types/editor.d.ts vendored

@ -0,0 +1,45 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/*
* @Author:
* @Date: 2023-05-13 18:53:44
* @LastEditors:
* @LastEditTime: 2023-06-27 22:44:31
* @Description: file content
*/
declare interface IPluginOption {
[propName: string]: unknown;
}
// 生命周期事件类型
declare type IEditorHooksType =
| "hookImportBefore"
| "hookImportAfter"
| "hookSaveBefore"
| "hookSaveAfter";
// 插件class
declare interface IPluginClass extends IPluginTempl {
new (
canvas: fabric.Canvas,
editor: any,
options: IPluginOption
): IPluginTempl;
}
declare interface IPluginMenu {
text: string;
command?: () => void;
child?: IPluginMenu[];
}
// 插件实例
declare interface IPluginTempl {
pluginName: string;
events: string[];
apis: string[];
canvas: fabric.Canvas;
hotkeyEvent: (name: string, e: Event) => viode;
[propName: IEditorHooksType]: () => void;
[propName: string]: any;
}

30
types/env.d.ts vendored

@ -0,0 +1,30 @@
/// <reference types="vite/client" />
// import { Object } from 'fabric/fabric-impl';
declare module "*.vue" {
import type { DefineComponent } from "vue";
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>;
export default component;
}
declare global {
declare module "fabric/fabric-impl" {
interface IObjectOptions {
/**
*
*/
id?: string | undefined;
}
}
}
export as namespace vfe;
declare module "vfe" {
export as namespace vfe;
export interface ICanvas extends fabric.Canvas {
c: fabric.Canvas;
editor: Editor;
}
}

35
types/extends.d.ts vendored

@ -0,0 +1,35 @@
declare namespace fabric {
export interface Canvas {
contextTop: CanvasRenderingContext2D;
lowerCanvasEl: HTMLElement;
_currentTransform: unknown;
_centerObject: (obj: fabric.Object, center: fabric.Point) => fabric.Canvas;
}
export interface Control {
rotate: number;
}
function ControlMouseEventHandler(
eventData: MouseEvent,
transformData: Transform,
x: number,
y: number
): boolean;
function ControlStringHandler(
eventData: MouseEvent,
control: fabric.Control,
fabricObject: fabric.Object
): string;
export const controlsUtils: {
rotationWithSnapping: ControlMouseEventHandler;
scalingEqually: ControlMouseEventHandler;
scalingYOrSkewingX: ControlMouseEventHandler;
scalingXOrSkewingY: ControlMouseEventHandler;
scaleCursorStyleHandler: ControlStringHandler;
scaleSkewCursorStyleHandler: ControlStringHandler;
scaleOrSkewActionName: ControlStringHandler;
rotationStyleHandler: ControlStringHandler;
};
}

@ -2,7 +2,7 @@
* @Author: donghao donghao@supervision.ltd
* @Date: 2024-02-22 13:38:05
* @LastEditors: donghao donghao@supervision.ltd
* @LastEditTime: 2024-02-22 14:18:31
* @LastEditTime: 2024-08-07 17:09:15
* @FilePath: \General-AI-Platform-Web-Client\vite.config.ts
* @Description: ,`customMade`, koroFileHeader : https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
@ -13,7 +13,7 @@ import { warpperEnv } from "./build";
import { getPluginsList } from "./build/plugins";
import { include, exclude } from "./build/optimize";
import { UserConfigExport, ConfigEnv, loadEnv } from "vite";
import autoImports from "unplugin-auto-import/vite";
/** 当前执行node命令时文件夹的地址工作目录 */
const root: string = process.cwd();
@ -23,10 +23,10 @@ const pathResolve = (dir: string): string => {
};
/** 设置别名 */
const alias: Record<string, string> = {
"@": pathResolve("src"),
"@build": pathResolve("build")
};
// const alias: Record<string, string> = {
// "@": pathResolve("src"),
// "@build": pathResolve("build")
// };
const { dependencies, devDependencies, name, version } = pkg;
const __APP_INFO__ = {
@ -41,7 +41,11 @@ export default ({ command, mode }: ConfigEnv): UserConfigExport => {
base: VITE_PUBLIC_PATH,
root,
resolve: {
alias
alias: [
{ find: /^@\//, replacement: resolve(__dirname, "src") + "/" },
{ find: /^~/, replacement: "" },
{ find: /^@build\//, replacement: pathResolve("build") }
]
},
// 服务端渲染
server: {
@ -60,7 +64,16 @@ export default ({ command, mode }: ConfigEnv): UserConfigExport => {
}
}
},
plugins: getPluginsList(command, VITE_CDN, VITE_COMPRESSION),
plugins: [
getPluginsList(command, VITE_CDN, VITE_COMPRESSION),
autoImports({
imports: ["vue"],
eslintrc: {
enabled: true
}
})
],
// https://cn.vitejs.dev/config/dep-optimization-options.html#dep-optimization-options
optimizeDeps: {
include,

Loading…
Cancel
Save