├── .browserslistrc
├── .dockerignore
├── .editorconfig
├── .env
├── .env.development
├── .env.production
├── .env.staging
├── .eslintrc-auto-import.json
├── .eslintrc.js
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── custom.md
│ └── feature_request.md
├── pull_request_template.md
└── workflows
│ ├── pull_request.yml
│ └── webpack.yml
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .npmrc
├── .prettierignore
├── .prettierrc.js
├── .vscode
├── extensions.json
└── settings.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README-en.md
├── README.md
├── commitlint.config.js
├── docker-compose.yml
├── index.html
├── nginx.conf
├── package.json
├── packages
└── core
│ ├── ContextMenu.js
│ ├── Editor.ts
│ ├── Instance.ts
│ ├── ServersPlugin.ts
│ ├── __tests__
│ ├── editor
│ │ └── basic.spec.ts
│ ├── env.d.ts
│ ├── plugin
│ │ └── DringPlugin.spec.ts
│ └── utils
│ │ ├── common.ts
│ │ └── setup.ts
│ ├── assets
│ ├── edgecontrol.svg
│ ├── lock.svg
│ ├── middlecontrol.svg
│ ├── middlecontrolhoz.svg
│ └── rotateicon.svg
│ ├── eventType.ts
│ ├── index.ts
│ ├── interface
│ └── Editor.ts
│ ├── objects
│ ├── Arrow.js
│ ├── CustomRect.js
│ ├── CustomTextbox.js
│ └── ThinTailArrow.js
│ ├── package.json
│ ├── plugin.ts
│ ├── plugin
│ ├── AddBaseTypePlugin.ts
│ ├── AlignGuidLinePlugin.ts
│ ├── BarCodePlugin.ts
│ ├── CenterAlignPlugin.ts
│ ├── ControlsPlugin.ts
│ ├── ControlsRotatePlugin.ts
│ ├── CopyPlugin.ts
│ ├── DeleteHotKeyPlugin.ts
│ ├── DrawLinePlugin.ts
│ ├── DrawPolygonPlugin.ts
│ ├── DringPlugin.ts
│ ├── FlipPlugin.ts
│ ├── FontPlugin.ts
│ ├── FreeDrawPlugin.ts
│ ├── GroupAlignPlugin.ts
│ ├── GroupPlugin.ts
│ ├── GroupTextEditorPlugin.ts
│ ├── HistoryPlugin.ts
│ ├── ImageStroke.ts
│ ├── LayerPlugin.ts
│ ├── LockPlugin.ts
│ ├── MaskPlugin.ts
│ ├── MaterialPlugin.ts
│ ├── MiddleMousePlugin.ts
│ ├── MoveHotKeyPlugin.ts
│ ├── PathTextPlugin.ts
│ ├── PolygonModifyPlugin.ts
│ ├── PsdPlugin.ts
│ ├── QrCodePlugin.ts
│ ├── ResizePlugin.ts
│ ├── RulerPlugin.ts
│ ├── SimpleClipImagePlugin.ts
│ ├── WaterMarkPlugin.ts
│ └── WorkspacePlugin.ts
│ ├── ruler
│ ├── guideline.ts
│ ├── index.ts
│ ├── ruler.ts
│ └── utils.ts
│ ├── styles
│ ├── contextMenu.css
│ └── resizePlugin.css
│ ├── utils
│ ├── fabric-history.js
│ ├── psd.js
│ └── utils.ts
│ ├── vite.config.ts
│ └── vitest.config.ts
├── pnpm-workspace.yaml
├── public
└── favicon.ico
├── src
├── App.vue
├── api
│ ├── admin.ts
│ ├── apiClass.ts
│ ├── material.ts
│ └── user.ts
├── assets
│ ├── filters
│ │ ├── BlackWhite.png
│ │ ├── Brownie.png
│ │ ├── Invert.png
│ │ ├── Kodachrome.png
│ │ ├── Polaroid.png
│ │ ├── Sepia.png
│ │ ├── Vintage.png
│ │ └── technicolor.png
│ ├── fonts
│ │ ├── cn
│ │ │ ├── 华康金刚黑.ttf
│ │ │ └── 汉体.ttf
│ │ ├── font.css
│ │ └── font.js
│ └── icon
│ │ ├── align
│ │ ├── averageX.svg
│ │ ├── averageY.svg
│ │ ├── bottom.svg
│ │ ├── centerX.svg
│ │ ├── centerY.svg
│ │ ├── left.svg
│ │ ├── right.svg
│ │ └── top.svg
│ │ ├── attribute
│ │ ├── fontStyle.svg
│ │ ├── fontWeight.svg
│ │ ├── linethrough.svg
│ │ ├── textAlignCenter.svg
│ │ ├── textAlignJustitfy.svg
│ │ ├── textAlignLeft.svg
│ │ ├── textAlignRight.svg
│ │ └── underline.svg
│ │ ├── barcode
│ │ ├── center.svg
│ │ ├── left.svg
│ │ └── right.svg
│ │ ├── bottom.svg
│ │ ├── centerAlign
│ │ ├── center.svg
│ │ ├── centerX.svg
│ │ └── centerY.svg
│ │ ├── centerx.svg
│ │ ├── centery.svg
│ │ ├── fileType.png
│ │ ├── flip
│ │ ├── x.svg
│ │ └── y.svg
│ │ ├── group
│ │ ├── group.svg
│ │ └── unGroup.svg
│ │ ├── layer
│ │ ├── circle.svg
│ │ ├── default.svg
│ │ ├── down.svg
│ │ ├── downTop.svg
│ │ ├── group.svg
│ │ ├── iText.svg
│ │ ├── image.svg
│ │ ├── polygon.svg
│ │ ├── rect.svg
│ │ ├── textbox.svg
│ │ ├── triangle.svg
│ │ ├── up.svg
│ │ └── upTop.svg
│ │ ├── left.svg
│ │ ├── proIcon.png
│ │ ├── right.svg
│ │ ├── sx.svg
│ │ ├── sy.svg
│ │ ├── tools
│ │ ├── barCode.svg
│ │ ├── circle.svg
│ │ ├── draw1.svg
│ │ ├── draw2.svg
│ │ ├── draw3.svg
│ │ ├── draw4.svg
│ │ ├── polygon.svg
│ │ ├── qrCode.svg
│ │ ├── rect.svg
│ │ ├── text.svg
│ │ ├── textBox.svg
│ │ └── triangle.svg
│ │ ├── top.svg
│ │ └── zoom
│ │ ├── big.svg
│ │ └── small.svg
├── components
│ ├── admin.vue
│ ├── align.vue
│ ├── attribute.vue
│ ├── attributeBarcode.vue
│ ├── attributeBorder.vue
│ ├── attributeColor.vue
│ ├── attributeFont.vue
│ ├── attributeId.vue
│ ├── attributePostion.vue
│ ├── attributeQrCode.vue
│ ├── attributeRounded.vue
│ ├── attributeShadow.vue
│ ├── attributeTextContent.vue
│ ├── attributeTextFloat.vue
│ ├── bgBar.vue
│ ├── centerAlign.vue
│ ├── clipImage.vue
│ ├── clone.vue
│ ├── color-picker
│ │ ├── comps
│ │ │ ├── AngleHandle.vue
│ │ │ ├── TabPanel.vue
│ │ │ ├── Tabs.vue
│ │ │ └── svg.vue
│ │ ├── index.css
│ │ ├── index.vue
│ │ └── utils
│ │ │ ├── color.ts
│ │ │ ├── helper.ts
│ │ │ ├── moveable.ts
│ │ │ └── tool.ts
│ ├── colorSelector.vue
│ ├── common
│ │ ├── modalSzie.vue
│ │ ├── pageList.vue
│ │ ├── searchType.vue
│ │ └── typeList.vue
│ ├── cropperDialog.vue
│ ├── cropperImg.vue
│ ├── del.vue
│ ├── dragMode.vue
│ ├── edit.vue
│ ├── filters.vue
│ ├── flip.vue
│ ├── fontStyle.vue
│ ├── group.vue
│ ├── hide.vue
│ ├── history.vue
│ ├── imgStroke.vue
│ ├── importFile.vue
│ ├── importJSON.vue
│ ├── importTmpl.vue
│ ├── inputNumber
│ │ ├── index.ts
│ │ └── inputNumber.vue
│ ├── lang.vue
│ ├── layer.vue
│ ├── lock.vue
│ ├── login.vue
│ ├── logo.vue
│ ├── material.vue
│ ├── myMaterial
│ │ ├── components
│ │ │ ├── file.vue
│ │ │ └── fileType.vue
│ │ ├── index.vue
│ │ ├── myTempl.vue
│ │ └── uploadMaterial.vue
│ ├── myTemplName.vue
│ ├── previewCurrent.vue
│ ├── replaceImg.vue
│ ├── save.vue
│ ├── setSize.vue
│ ├── svgIcon
│ │ ├── index.js
│ │ └── index.vue
│ ├── tools.vue
│ ├── waterMark.vue
│ ├── workspaceMask.vue
│ └── zoom.vue
├── config
│ └── constants
│ │ ├── app.ts
│ │ └── filter.ts
├── hooks
│ ├── pageList.js
│ ├── select.ts
│ ├── useAdmin.js
│ ├── useCalculate.js
│ ├── useFileType.js
│ ├── useMaterial.js
│ ├── usePageList.js
│ └── useSelectListen.ts
├── language
│ ├── en.json
│ ├── index.ts
│ ├── pt.json
│ └── zh.json
├── main.ts
├── router
│ ├── index.ts
│ └── routes.ts
├── styles
│ ├── index.less
│ ├── resetViewUi.less
│ └── variable.less
├── utils
│ ├── local.ts
│ └── math.ts
└── views
│ ├── home
│ ├── components
│ │ ├── left
│ │ │ └── index.vue
│ │ ├── right
│ │ │ └── index.vue
│ │ └── top
│ │ │ └── index.vue
│ ├── index.module.less
│ └── index.vue
│ └── template
│ ├── components
│ └── banner.vue
│ ├── index.module.less
│ └── index.vue
├── tsconfig.json
├── tsconfig.node.json
├── typings
├── env.d.ts
├── extends.d.ts
└── modules.ts
└── vite.config.ts
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | # path: ./.dockerignore
2 |
3 | .DS_Store
4 | node_modules
5 | dist
6 | .npmrc
7 | yarn.lock
8 | pnpm-lock.yaml
9 | package-lock.json
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | end_of_line = lf
5 | trim_trailing_whitespace = true
6 | insert_final_newline = true
7 | max_line_length = 100
8 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | APP_TITLE=快图设计-开源图片编辑器-vue-fabric-editor
2 | APP_BASE_PATH=/vue-fabric-editor/
3 | APP_APIHOST=https://github.kuaitu.cc
4 | APP_ADMINAPIHOST=https://github.kuaitu.cc
5 |
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | APP_FLAG=dev
2 | # APP_APIHOST=https://github.kuaitu.cc
3 | # APP_APIHOST=http://localhost:1337
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | APP_FLAG=prod
--------------------------------------------------------------------------------
/.env.staging:
--------------------------------------------------------------------------------
1 | APP_FLAG=staging
--------------------------------------------------------------------------------
/.eslintrc-auto-import.json:
--------------------------------------------------------------------------------
1 | {
2 | "globals": {
3 | "Component": true,
4 | "ComponentPublicInstance": true,
5 | "ComputedRef": true,
6 | "DirectiveBinding": true,
7 | "EffectScope": true,
8 | "ExtractDefaultPropTypes": true,
9 | "ExtractPropTypes": true,
10 | "ExtractPublicPropTypes": true,
11 | "InjectionKey": true,
12 | "MaybeRef": true,
13 | "MaybeRefOrGetter": true,
14 | "PropType": true,
15 | "Ref": true,
16 | "VNode": true,
17 | "WritableComputedRef": true,
18 | "computed": true,
19 | "createApp": true,
20 | "customRef": true,
21 | "defineAsyncComponent": true,
22 | "defineComponent": true,
23 | "effectScope": true,
24 | "getCurrentInstance": true,
25 | "getCurrentScope": true,
26 | "h": true,
27 | "inject": true,
28 | "isProxy": true,
29 | "isReactive": true,
30 | "isReadonly": true,
31 | "isRef": true,
32 | "markRaw": true,
33 | "nextTick": true,
34 | "onActivated": true,
35 | "onBeforeMount": true,
36 | "onBeforeUnmount": true,
37 | "onBeforeUpdate": true,
38 | "onDeactivated": true,
39 | "onErrorCaptured": true,
40 | "onMounted": true,
41 | "onRenderTracked": true,
42 | "onRenderTriggered": true,
43 | "onScopeDispose": true,
44 | "onServerPrefetch": true,
45 | "onUnmounted": true,
46 | "onUpdated": true,
47 | "onWatcherCleanup": true,
48 | "provide": true,
49 | "reactive": true,
50 | "readonly": true,
51 | "ref": true,
52 | "resolveComponent": true,
53 | "shallowReactive": true,
54 | "shallowReadonly": true,
55 | "shallowRef": true,
56 | "toRaw": true,
57 | "toRef": true,
58 | "toRefs": true,
59 | "toValue": true,
60 | "triggerRef": true,
61 | "unref": true,
62 | "useAttrs": true,
63 | "useCssModule": true,
64 | "useCssVars": true,
65 | "useId": true,
66 | "useModel": true,
67 | "useSlots": true,
68 | "useTemplateRef": true,
69 | "watch": true,
70 | "watchEffect": true,
71 | "watchPostEffect": true,
72 | "watchSyncEffect": true
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true,
5 | node: true,
6 | },
7 | extends: [
8 | 'eslint:recommended',
9 | 'plugin:vue/vue3-essential',
10 | 'plugin:@typescript-eslint/recommended',
11 | 'plugin:prettier/recommended',
12 | './.eslintrc-auto-import.json',
13 | ],
14 | parser: 'vue-eslint-parser',
15 | parserOptions: {
16 | ecmaVersion: 'latest',
17 | parser: '@typescript-eslint/parser',
18 | sourceType: 'module',
19 | },
20 | plugins: ['vue', '@typescript-eslint'],
21 | rules: {
22 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
23 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
24 | 'no-plusplus': 'off',
25 | '@typescript-eslint/no-this-alias': 'off',
26 | '@typescript-eslint/no-var-requires': 'off',
27 | '@typescript-eslint/no-explicit-any': 'off',
28 | 'import/no-unresolved': 'off',
29 | 'vuejs-accessibility/form-control-has-label': 'off',
30 | 'consistent-return': 'off', // 强制统一返回值
31 | 'no-param-reassign': 'off', // 参数重新分配
32 | 'no-underscore-dangle': 'off', // 使用下划线命名
33 | 'comma-spacing': 'off',
34 | 'vuejs-accessibility/click-events-have-key-events': 'off',
35 | 'max-len': 'off',
36 | 'no-unused-expressions': 'off', // 17
37 | 'linebreak-style': 'off',
38 | 'vue/multi-word-component-names': 'off', // 开启组件需要多单词
39 | 'vue/no-setup-props-destructure': 'off',
40 | 'vuejs-accessibility/anchor-has-content': 'off',
41 | '@typescript-eslint/no-non-null-assertion': 'off',
42 | },
43 | overrides: [
44 | {
45 | files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)'],
46 | env: {
47 | jest: true,
48 | },
49 | },
50 | ],
51 | };
52 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: ['https://oss.issuehunt.io/r/nihaojob/vue-fabric-editor'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Custom issue template
3 | about: Describe this issue template's purpose here.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## 贡献者你好
2 |
3 | 很高兴你能付出自己的时间参与到 vue-fabric-editor 的共建当中,相信很多人都会因为你提交的代码而受益。
4 |
5 | ### 原则
6 |
7 | 我们希望每次提交尽量小(较大重构除外),确保每次改动的影响范围清晰明了,能够方便项目维护者快速将代码合并到主分支。
8 |
9 | ### 确保你的代码与主仓库没有冲突
10 |
11 | 在 PR 前,请确保你的代码与主仓库保持同步,可以参考这篇[文章](https://zhuanlan.zhihu.com/p/467670042)。
12 |
13 | ### 确保你的代码能正常打包构建
14 |
15 | 在 PR 前,请在本地进行打包构建,并进行功能测试,确保功能正常,且不影响其他功能。
16 |
17 | - [ ] 代码构建正常
18 |
19 | ### 告知项目维护者本次修改的功能
20 |
21 | 请在下方描述你的修改内容:
22 |
--------------------------------------------------------------------------------
/.github/workflows/pull_request.yml:
--------------------------------------------------------------------------------
1 | name: NodeJS with Webpack
2 |
3 | on:
4 | pull_request:
5 | branches: ['main']
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v3
14 |
15 | - name: Build
16 | run: |
17 | npm i -g pnpm
18 | pnpm install
19 | npm run build
20 |
--------------------------------------------------------------------------------
/.github/workflows/webpack.yml:
--------------------------------------------------------------------------------
1 | name: NodeJS with Webpack
2 |
3 | on:
4 | push:
5 | branches: ['main']
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v3
14 |
15 | - name: Build
16 | run: |
17 | npm i -g pnpm
18 | pnpm install
19 | pnpm build
20 |
21 | - name: Deploy
22 | uses: peaceiris/actions-gh-pages@v3
23 | with:
24 | personal_token: ${{ secrets.demo }}
25 | publish_dir: ./dist
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | dist
4 | typings/auto-imports.d.ts
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
25 | yarn.lock
26 | # pnpm-lock.yaml # https://github.com/ikuaitu/vue-fabric-editor/issues/554
27 | package-lock.json
28 | typings/auto-imports.d.ts
29 | .eslintrc-auto-import.json
30 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx --no -- commitlint --edit ${1}
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx lint-staged
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry = https://registry.npmmirror.com # 镜像源
2 | canvas_binary_host_mirror = https://registry.npmmirror.com/-/binary/canvas # canvas安装慢的问题
3 |
4 | ignore-workspace-root-check = true
5 | shamefully-hoist = true
6 | strict-peer-dependencies = false
7 | auto-install-peers = true
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
25 | yarn.lock
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 100, // 指定代码长度,超出换行
3 | tabWidth: 2, // tab 键的宽度
4 | useTabs: false, // 不使用tab
5 | semi: true, // 结尾加上分号
6 | singleQuote: true, // 使用单引号
7 | quoteProps: 'as-needed', // 要求对象字面量属性是否使用引号包裹,(‘as-needed’: 没有特殊要求,禁止使用,'consistent': 保持一致 , preserve: 不限制,想用就用)
8 | jsxSingleQuote: false, // jsx 语法中使用单引号
9 | trailingComma: 'es5', // 确保对象的最后一个属性后有逗号
10 | bracketSpacing: true, // 大括号有空格 { name: 'rose' }
11 | jsxBracketSameLine: false, // 在多行JSX元素的最后一行追加 >
12 | arrowParens: 'always', // 箭头函数,单个参数添加括号
13 | requirePragma: false, // 是否严格按照文件顶部的特殊注释格式化代码
14 | insertPragma: false, // 是否在格式化的文件顶部插入Pragma标记,以表明该文件被prettier格式化过了
15 | proseWrap: 'preserve', // 按照文件原样折行
16 | htmlWhitespaceSensitivity: 'ignore', // html文件的空格敏感度,控制空格是否影响布局
17 | endOfLine: 'auto', // 结尾是 \n \r \n\r auto
18 | };
19 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint",
4 | "esbenp.prettier-vscode",
5 | "lokalise.i18n-ally",
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "eslint.enable": true,
3 | "editor.codeActionsOnSave": {
4 | "source.fixAll.eslint": "explicit"
5 | },
6 | "editor.formatOnSave": true,
7 | "editor.defaultFormatter": "esbenp.prettier-vscode",
8 | "prettier.configPath": ".prettierrc.js",
9 | "prettier.requireConfig": true,
10 | "i18n-ally.localesPaths": [
11 | "src/language"
12 | ],
13 | "i18n-ally.sourceLanguage": "zh",
14 | "i18n-ally.keystyle": "nested",
15 | "[vue]": {
16 | "editor.defaultFormatter": "esbenp.prettier-vscode"
17 | } // 需要Prettier的配置文件
18 | }
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # 贡献指南
2 |
3 | 你好!我们很高兴你有兴趣为 vue-fabric-editor 做出贡献。在提交你的贡献之前,请花点时间阅读以下指南:
4 |
5 | ## 快速上手
6 | vue-fabric-editor 依赖Node.js v16版本进行开发,要确保你本地已安装Node.js。
7 |
8 | ## 第一次贡献
9 |
10 | 如果你还不清楚怎么在 GitHub 上提 Pull Request ,可以阅读下面这篇文章来学习:
11 |
12 | [如何优雅地在 GitHub 上贡献代码](https://segmentfault.com/a/1190000000736629)
13 |
14 | 为了能帮助你开始你的第一次尝试,我们用 [good first issues](https://github.com/nihaojob/vue-fabric-editor/labels/good%20first%20issue) 标记了一些比较容易修复的 bug 和小功能。这些 issue 可以很好地作为你的首次尝试。
15 |
16 | 如果你打算开始处理一个 issue,请先检查一下 issue 下面的留言以确保没有别人正在处理这个 issue。如果当前没有人在处理的话你可以留言告知其他人你将会处理这个 issue,以免别人重复劳动。
17 |
18 | 如果之前有人留言说会处理这个 issue 但是一两个星期都没有动静,那么你也可以接手处理这个 issue,当然还是需要留言告知其他人。
19 |
20 | ## Pull Request
21 |
22 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine3.18 as build
2 |
3 | WORKDIR /app
4 | COPY . .
5 |
6 | RUN npm install -g pnpm --registry=https://registry.npmmirror.com
7 | RUN pnpm config set registry https://registry.npmmirror.com
8 | RUN pnpm install
9 |
10 | RUN ["pnpm", "build"]
11 |
12 | FROM nginx:1.25.3
13 | COPY --from=0 /app/dist /usr/share/nginx/html/
14 | COPY nginx.conf /etc/nginx/conf.d/
15 |
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 秦少卫
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional'],
3 | };
4 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | kuaitu:
4 | container_name: kuaitu
5 | build: ./
6 | ports:
7 | - '8888:80'
8 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | <%- APP_TITLE %>
19 |
21 |
22 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
55 |
--------------------------------------------------------------------------------
/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | server_name localhost;
4 |
5 | location / {
6 | root /usr/share/nginx/html;
7 | index index.html;
8 | try_files $uri $uri/ /index.html;
9 | }
10 |
11 | error_page 500 502 503 504 /50x.html;
12 | location = /50x.html {
13 | root /usr/share/nginx/html;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "view-ui-project-ts",
3 | "private": true,
4 | "version": "1.0.0",
5 | "description": "a template project for vue3, ViewUIPlus, TypeScript and Vite.",
6 | "scripts": {
7 | "preinstall": "npx only-allow pnpm",
8 | "serve": "pnpm dev",
9 | "dev": "vite serve",
10 | "dev:staging": "vite serve --mode=staging",
11 | "dev:prod": "vite serve --mode=production",
12 | "build": "vite build",
13 | "build:sdk": "pnpm -C packages/core build",
14 | "build:staging": "vite build --mode=staging",
15 | "preview": "pnpm build && vite preview",
16 | "preview:staging": "pnpm build:staging && vite preview --mode=staging",
17 | "prepare": "husky install",
18 | "test": "pnpm -C packages/core test"
19 | },
20 | "dependencies": {
21 | "@kuaitu/core": "workspace:^",
22 | "@vueuse/core": "^10.1.0",
23 | "axios": "^1.3.4",
24 | "color-gradient-picker-vue3": "^3.1.0",
25 | "dayjs": "^1.11.11",
26 | "events": "^3.3.0",
27 | "fabric": "^5.3.0",
28 | "fontfaceobserver": "^2.1.0",
29 | "lodash-es": "^4.17.21",
30 | "number-precision": "^1.6.0",
31 | "qs": "^6.12.1",
32 | "uuid": "^8.3.2",
33 | "view-ui-plus": "1.3.7",
34 | "vite-svg-loader": "^5.1.0",
35 | "vue": "^3.2.25",
36 | "vue-cropper": "^1.1.4",
37 | "vue-i18n": "9.0.0",
38 | "vue-masonry": "^0.16.0",
39 | "vue-router": "^4.0.16",
40 | "vue3-lazyload": "^0.3.6"
41 | },
42 | "workspaces": [
43 | "packages/*"
44 | ],
45 | "devDependencies": {
46 | "@commitlint/cli": "^17.4.2",
47 | "@commitlint/config-conventional": "^17.4.2",
48 | "@types/events": "^3.0.0",
49 | "@types/fabric": "^5.3.0",
50 | "@types/fontfaceobserver": "^2.1.3",
51 | "@types/lodash-es": "^4.17.7",
52 | "@types/node": "^18.15.13",
53 | "@types/uuid": "^9.0.1",
54 | "@typescript-eslint/eslint-plugin": "^5.54.1",
55 | "@typescript-eslint/parser": "^5.54.1",
56 | "@vitejs/plugin-vue": "^4.1.0",
57 | "@vitejs/plugin-vue-jsx": "^3.0.1",
58 | "autoprefixer": "^10.4.16",
59 | "eslint": "^7.32.0",
60 | "eslint-config-prettier": "^8.6.0",
61 | "eslint-plugin-prettier": "^4.2.1",
62 | "eslint-plugin-vue": "^9.9.0",
63 | "husky": "^8.0.0",
64 | "less": "^4.1.3",
65 | "less-loader": "^11.1.0",
66 | "lint-staged": "^13.1.1",
67 | "prettier": "2.8.4",
68 | "typescript": "5.1.6",
69 | "unplugin-auto-import": "^0.16.0",
70 | "vite": "^4.2.1",
71 | "vite-plugin-compression": "^0.5.1",
72 | "vite-plugin-eslint": "^1.8.1",
73 | "vite-plugin-html": "^3.2.0",
74 | "vite-plugin-vue-setup-extend-plus": "^0.1.0",
75 | "vue-tsc": "^0.34.7"
76 | },
77 | "lint-staged": {
78 | "*.{ts,tsx,js,vue}": [
79 | "eslint --fix",
80 | "prettier --write"
81 | ]
82 | },
83 | "pnpm": {
84 | "overrides": {
85 | "@jridgewell/gen-mapping": "0.3.5"
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/packages/core/Instance.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 秦少卫
3 | * @Date: 2024-04-10 15:38:47
4 | * @LastEditors: 秦少卫
5 | * @LastEditTime: 2024-07-22 11:56:28
6 | * @Description: 类型文件
7 | */
8 | import Editor from './Editor';
9 | import DringPlugin from './plugin/DringPlugin';
10 | import AlignGuidLinePlugin from './plugin/AlignGuidLinePlugin';
11 | import ControlsPlugin from './plugin/ControlsPlugin';
12 | import ControlsRotatePlugin from './plugin/ControlsRotatePlugin';
13 | import CenterAlignPlugin from './plugin/CenterAlignPlugin';
14 | import LayerPlugin from './plugin/LayerPlugin';
15 | import CopyPlugin from './plugin/CopyPlugin';
16 | import MoveHotKeyPlugin from './plugin/MoveHotKeyPlugin';
17 | import DeleteHotKeyPlugin from './plugin/DeleteHotKeyPlugin';
18 | import GroupPlugin from './plugin/GroupPlugin';
19 | import DrawLinePlugin from './plugin/DrawLinePlugin';
20 | import GroupTextEditorPlugin from './plugin/GroupTextEditorPlugin';
21 | import GroupAlignPlugin from './plugin/GroupAlignPlugin';
22 | import WorkspacePlugin from './plugin/WorkspacePlugin';
23 | import MaskPlugin from './plugin/MaskPlugin';
24 | import HistoryPlugin from './plugin/HistoryPlugin';
25 | import FlipPlugin from './plugin/FlipPlugin';
26 | import RulerPlugin from './plugin/RulerPlugin';
27 | import MaterialPlugin from './plugin/MaterialPlugin';
28 | import WaterMarkPlugin from './plugin/WaterMarkPlugin';
29 | import FontPlugin from './plugin/FontPlugin';
30 | import PolygonModifyPlugin from './plugin/PolygonModifyPlugin';
31 | import DrawPolygonPlugin from './plugin/DrawPolygonPlugin';
32 | import FreeDrawPlugin from './plugin/FreeDrawPlugin';
33 | import PathTextPlugin from './plugin/PathTextPlugin';
34 | import PsdPlugin from './plugin/PsdPlugin';
35 | import SimpleClipImagePlugin from './plugin/SimpleClipImagePlugin';
36 | import BarCodePlugin from './plugin/BarCodePlugin';
37 | import QrCodePlugin from './plugin/QrCodePlugin';
38 | import ImageStroke from './plugin/ImageStroke';
39 | import ResizePlugin from './plugin/ResizePlugin';
40 | import LockPlugin from './plugin/LockPlugin';
41 | import AddBaseTypePlugin from './plugin/AddBaseTypePlugin';
42 |
43 | const AllEditor = {
44 | Editor,
45 | DringPlugin,
46 | AlignGuidLinePlugin,
47 | ControlsPlugin,
48 | ControlsRotatePlugin,
49 | CenterAlignPlugin,
50 | LayerPlugin,
51 | CopyPlugin,
52 | MoveHotKeyPlugin,
53 | DeleteHotKeyPlugin,
54 | GroupPlugin,
55 | DrawLinePlugin,
56 | GroupTextEditorPlugin,
57 | GroupAlignPlugin,
58 | WorkspacePlugin,
59 | MaskPlugin,
60 | HistoryPlugin,
61 | FlipPlugin,
62 | RulerPlugin,
63 | MaterialPlugin,
64 | WaterMarkPlugin,
65 | FontPlugin,
66 | PolygonModifyPlugin,
67 | DrawPolygonPlugin,
68 | FreeDrawPlugin,
69 | PathTextPlugin,
70 | PsdPlugin,
71 | SimpleClipImagePlugin,
72 | BarCodePlugin,
73 | QrCodePlugin,
74 | ImageStroke,
75 | ResizePlugin,
76 | LockPlugin,
77 | AddBaseTypePlugin,
78 | };
79 |
80 | declare type KuaituEditor = typeof AllEditor;
81 |
82 | export default KuaituEditor;
83 |
--------------------------------------------------------------------------------
/packages/core/__tests__/editor/basic.spec.ts:
--------------------------------------------------------------------------------
1 | import { beforeEach, expect, test } from 'vitest';
2 | import { createEditor } from '../utils/setup.ts';
3 |
4 | const { cleanUp } = createEditor();
5 |
6 | beforeEach(() => {
7 | return cleanUp;
8 | });
9 |
10 | test('basic', () => {
11 | expect(window.editor).toBeDefined();
12 | });
13 |
--------------------------------------------------------------------------------
/packages/core/__tests__/env.d.ts:
--------------------------------------------------------------------------------
1 | import Editor from '../Editor';
2 |
3 | declare global {
4 | interface Window {
5 | editor: Editor;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/packages/core/__tests__/plugin/DringPlugin.spec.ts:
--------------------------------------------------------------------------------
1 | import { beforeEach, expect, test } from 'vitest';
2 | import { initPlugin } from '../utils/setup.ts';
3 | import { DringPlugin } from '../../plugin/DringPlugin.ts';
4 | import { wait, mouseDown, drag } from '../utils/common.ts';
5 | import { describe } from 'node:test';
6 |
7 | describe('canvas:drag', async () => {
8 | const { pluginInstance, cleanUp } = initPlugin(DringPlugin);
9 |
10 | beforeEach(() => {
11 | return () => {
12 | cleanUp();
13 | (pluginInstance as DringPlugin).destroy();
14 | };
15 | });
16 | test('dragMode is true', async () => {
17 | const instance = pluginInstance as DringPlugin;
18 | instance.dragMode = true;
19 | const testCanvas = instance.canvas as ExtCanvas;
20 | testCanvas.lastPosX = 0;
21 | testCanvas.lastPosY = 0;
22 | mouseDown(testCanvas, { x: 50, y: 50 });
23 | await wait();
24 | expect([testCanvas.lastPosX, testCanvas.lastPosY]).toEqual([50, 50]);
25 | expect(testCanvas.isDragging).toEqual(true);
26 | expect(testCanvas.selection).toEqual(false);
27 | });
28 |
29 | test('dragMode is false', async () => {
30 | const instance = pluginInstance as DringPlugin;
31 | instance.dragMode = false;
32 | const testCanvas = instance.canvas as ExtCanvas;
33 | testCanvas.lastPosX = 0;
34 | testCanvas.lastPosY = 0;
35 | mouseDown(testCanvas, { x: 50, y: 50 });
36 | await wait();
37 | expect([testCanvas.lastPosX, testCanvas.lastPosY]).toEqual([0, 0]);
38 | });
39 |
40 | test('canvas drag', async () => {
41 | const instance = pluginInstance as DringPlugin;
42 | instance.dragMode = false;
43 | const testCanvas = pluginInstance.canvas as ExtCanvas;
44 | testCanvas.lastPosX = 0;
45 | testCanvas.lastPosY = 0;
46 | drag(testCanvas, { x: 50, y: 50 }, { x: 100, y: 100 });
47 | await wait();
48 | expect([testCanvas.lastPosX, testCanvas.lastPosY]).toEqual([100, 100]);
49 | expect(testCanvas.isDragging).toEqual(false);
50 | expect(testCanvas.selection).toEqual(true);
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/packages/core/__tests__/utils/common.ts:
--------------------------------------------------------------------------------
1 | export function wait(time = 0) {
2 | return new Promise((resolve) => {
3 | requestAnimationFrame(() => {
4 | setTimeout(resolve, time);
5 | });
6 | });
7 | }
8 |
9 | /**
10 | * simulate mouseDown event
11 | * @param target
12 | * @param position position relative to the target
13 | */
14 | export function mouseDown(target: ExtCanvas, position: { x: number; y: number }) {
15 | const clientX = target.lastPosX + position.x;
16 | const clientY = target.lastPosY + position.y;
17 | target.fire('mouse:down', { e: { clientX: clientX, clientY: clientY } });
18 | }
19 |
20 | /**
21 | * simulate mouseMove event
22 | * @param target
23 | * @param position
24 | */
25 |
26 | export function mouseMove(target: ExtCanvas, position: { x: number; y: number }) {
27 | const clientX = position.x;
28 | const clientY = position.y;
29 | target.fire('mouse:move', { e: { clientX: clientX, clientY: clientY } });
30 | }
31 |
32 | /**
33 | * simulate mouseUp event
34 | * @param target
35 | */
36 |
37 | export function mouseUp(target: ExtCanvas) {
38 | target.fire('mouse:up');
39 | }
40 |
41 | export function drag(
42 | target: ExtCanvas,
43 | start: { x: number; y: number },
44 | end: { x: number; y: number },
45 | step = 5
46 | ) {
47 | mouseDown(target, start);
48 | mouseMove(target, start);
49 | if (step !== 0) {
50 | const xStep = (end.x - start.x) / step;
51 | const yStep = (end.y - start.y) / step;
52 |
53 | for (const [i] of Array.from({ length: step }).entries()) {
54 | mouseMove(target, {
55 | x: xStep * (i + 1),
56 | y: yStep * (i + 1),
57 | });
58 | }
59 | }
60 |
61 | mouseMove(target, end);
62 | mouseUp(target);
63 | }
64 |
--------------------------------------------------------------------------------
/packages/core/__tests__/utils/setup.ts:
--------------------------------------------------------------------------------
1 | import Editor from '../../Editor.ts';
2 | import { fabric } from 'fabric';
3 |
4 | export function createEditor() {
5 | const editor = new Editor();
6 | const canvasElement = document.createElement('canvas');
7 | canvasElement.id = 'canvas';
8 | const canvas = new fabric.Canvas('canvas', {
9 | fireRightClick: true,
10 | stopContextMenu: true,
11 | controlsAboveOverlay: true,
12 | imageSmoothingEnabled: false,
13 | preserveObjectStacking: true,
14 | });
15 | editor.init(canvas);
16 | window.editor = editor;
17 |
18 | return {
19 | cleanUp: editor.destory(),
20 | };
21 | }
22 |
23 | export function initPlugin(plugin: any) {
24 | const editor = new Editor();
25 | const canvasElement = document.createElement('canvas');
26 | canvasElement.id = 'canvas';
27 | const canvas = new fabric.Canvas('canvas', {});
28 | const pluginInstance = new plugin(canvas, editor);
29 |
30 | return {
31 | pluginInstance,
32 | cleanUp: () => {
33 | editor.destory();
34 | },
35 | };
36 | }
37 |
--------------------------------------------------------------------------------
/packages/core/assets/edgecontrol.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/packages/core/assets/lock.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/core/assets/middlecontrol.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/packages/core/assets/middlecontrolhoz.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/packages/core/assets/rotateicon.svg:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/packages/core/eventType.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 秦少卫
3 | * @Date: 2024-04-10 14:00:05
4 | * @LastEditors: 秦少卫
5 | * @LastEditTime: 2024-04-10 14:01:39
6 | * @Description: 事件类型
7 | */
8 | // 选择模式
9 | export enum SelectMode {
10 | EMPTY = '',
11 | ONE = 'one',
12 | MULTI = 'multiple',
13 | }
14 |
15 | // 选择事件(用于广播)
16 | export enum SelectEvent {
17 | ONE = 'selectOne',
18 | MULTI = 'selectMultiple',
19 | CANCEL = 'selectCancel',
20 | }
21 |
22 | export default { SelectMode, SelectEvent };
23 |
--------------------------------------------------------------------------------
/packages/core/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 秦少卫
3 | * @Date: 2023-02-03 23:29:34
4 | * @LastEditors: 秦少卫
5 | * @LastEditTime: 2024-07-06 12:36:22
6 | * @Description: 核心入口文件
7 | */
8 | import Editor from './Editor';
9 | export { default as DringPlugin } from './plugin/DringPlugin';
10 | export { default as AlignGuidLinePlugin } from './plugin/AlignGuidLinePlugin';
11 | export { default as ControlsPlugin } from './plugin/ControlsPlugin';
12 | export { default as ControlsRotatePlugin } from './plugin/ControlsRotatePlugin';
13 | export { default as CenterAlignPlugin } from './plugin/CenterAlignPlugin';
14 | export { default as LayerPlugin } from './plugin/LayerPlugin';
15 | export { default as CopyPlugin } from './plugin/CopyPlugin';
16 | export { default as MoveHotKeyPlugin } from './plugin/MoveHotKeyPlugin';
17 | export { default as DeleteHotKeyPlugin } from './plugin/DeleteHotKeyPlugin';
18 | export { default as GroupPlugin } from './plugin/GroupPlugin';
19 | export { default as DrawLinePlugin } from './plugin/DrawLinePlugin';
20 | export { default as GroupTextEditorPlugin } from './plugin/GroupTextEditorPlugin';
21 | export { default as GroupAlignPlugin } from './plugin/GroupAlignPlugin';
22 | export { default as WorkspacePlugin } from './plugin/WorkspacePlugin';
23 | export { default as MaskPlugin } from './plugin/MaskPlugin';
24 | export { default as HistoryPlugin } from './plugin/HistoryPlugin';
25 | export { default as FlipPlugin } from './plugin/FlipPlugin';
26 | export { default as RulerPlugin } from './plugin/RulerPlugin';
27 | export { default as MaterialPlugin } from './plugin/MaterialPlugin';
28 | export { default as WaterMarkPlugin } from './plugin/WaterMarkPlugin';
29 | export { default as FontPlugin } from './plugin/FontPlugin';
30 | export { default as PolygonModifyPlugin } from './plugin/PolygonModifyPlugin';
31 | export { default as DrawPolygonPlugin } from './plugin/DrawPolygonPlugin';
32 | export { default as FreeDrawPlugin } from './plugin/FreeDrawPlugin';
33 | export { default as PathTextPlugin } from './plugin/PathTextPlugin';
34 | export { default as PsdPlugin } from './plugin/PsdPlugin';
35 | export { default as SimpleClipImagePlugin } from './plugin/SimpleClipImagePlugin';
36 | export { default as BarCodePlugin } from './plugin/BarCodePlugin';
37 | export { default as QrCodePlugin } from './plugin/QrCodePlugin';
38 | export { default as ImageStroke } from './plugin/ImageStroke';
39 | export { default as ResizePlugin } from './plugin/ResizePlugin';
40 | export { default as LockPlugin } from './plugin/LockPlugin';
41 | export { default as AddBaseTypePlugin } from './plugin/AddBaseTypePlugin';
42 | import EventType from './eventType';
43 | import Utils from './utils/utils';
44 | import CustomRect from './objects/CustomRect';
45 | import CustomTextbox from './objects/CustomTextbox';
46 | // import { extend } from 'dayjs';
47 |
48 | export { EventType, Utils, CustomRect, CustomTextbox };
49 | export default Editor;
50 |
51 | export * from './interface/Editor';
52 |
--------------------------------------------------------------------------------
/packages/core/interface/Editor.ts:
--------------------------------------------------------------------------------
1 | import type Editor from '@kuaitu/core';
2 |
3 | // IEditor类型包含插件实例,Editor不包含插件实例
4 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
5 | export interface IEditor extends Editor {}
6 |
7 | // 生命周期事件类型
8 | export type IEditorHooksType =
9 | | 'hookImportBefore'
10 | | 'hookImportAfter'
11 | | 'hookSaveBefore'
12 | | 'hookSaveAfter'
13 | | 'hookTransform';
14 |
15 | // 插件实例
16 | export declare class IPluginTempl {
17 | constructor(canvas: fabric.Canvas, editor: IEditor, options?: IPluginOption);
18 | static pluginName: string;
19 | static events: string[];
20 | static apis: string[];
21 | hotkeyEvent?: (name: string, e: KeyboardEvent) => void;
22 | hookImportBefore?: (...args: unknown[]) => Promise;
23 | hookImportAfter?: (...args: unknown[]) => Promise;
24 | hookSaveBefore?: (...args: unknown[]) => Promise;
25 | hookSaveAfter?: (...args: unknown[]) => Promise;
26 | hookTransform?: (...args: unknown[]) => Promise;
27 | [propName: string]: any;
28 | canvas?: fabric.Canvas;
29 | editor?: IEditor;
30 | }
31 |
32 | export declare interface IPluginOption {
33 | [propName: string]: unknown | undefined;
34 | }
35 |
36 | declare class IPluginClass2 extends IPluginTempl {
37 | constructor();
38 | }
39 | // 插件class
40 | export declare interface IPluginClass {
41 | new (canvas: fabric.Canvas, editor: Editor, options?: IPluginOption): IPluginClass2;
42 | }
43 |
44 | export declare interface IPluginMenu {
45 | text: string;
46 | command?: () => void;
47 | child?: IPluginMenu[];
48 | }
49 |
--------------------------------------------------------------------------------
/packages/core/objects/Arrow.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 秦少卫
3 | * @Date: 2023-01-07 01:15:50
4 | * @LastEditors: 秦少卫
5 | * @LastEditTime: 2023-02-08 00:08:40
6 | * @Description: 箭头元素
7 | */
8 | import { fabric } from 'fabric';
9 |
10 | fabric.Arrow = fabric.util.createClass(fabric.Line, {
11 | type: 'arrow',
12 | superType: 'drawing',
13 | initialize(points, options) {
14 | if (!points) {
15 | const { x1, x2, y1, y2 } = options;
16 | points = [x1, y1, x2, y2];
17 | }
18 | options = options || {};
19 | this.callSuper('initialize', points, options);
20 | },
21 | _render(ctx) {
22 | this.callSuper('_render', ctx);
23 | ctx.save();
24 | // 乘或除对应的scaleX(Y),抵消元素放缩造成的影响,使箭头不会变形
25 | ctx.scale(1 / this.scaleX, 1 / this.scaleY);
26 | const xDiff = (this.x2 - this.x1) * this.scaleX;
27 | const yDiff = (this.y2 - this.y1) * this.scaleY;
28 | const angle = Math.atan2(yDiff, xDiff);
29 | ctx.translate(((this.x2 - this.x1) / 2) * this.scaleX, ((this.y2 - this.y1) / 2) * this.scaleY);
30 | ctx.rotate(angle);
31 | ctx.beginPath();
32 | // Move 5px in front of line to start the arrow so it does not have the square line end showing in front (0,0)
33 | ctx.moveTo(5, 0);
34 | ctx.lineTo(-5, 5);
35 | ctx.lineTo(-5, -5);
36 | ctx.closePath();
37 | ctx.lineWidth = this.lineWidth;
38 | ctx.strokeStyle = this.stroke;
39 | ctx.fillStyle = this.fill;
40 | ctx.stroke();
41 | ctx.fill();
42 | ctx.restore();
43 | },
44 | });
45 |
46 | fabric.Arrow.fromObject = (options, callback) => {
47 | const { x1, x2, y1, y2 } = options;
48 | return callback(new fabric.Arrow([x1, y1, x2, y2], options));
49 | };
50 |
51 | export default fabric.Arrow;
52 |
--------------------------------------------------------------------------------
/packages/core/objects/CustomRect.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: wuchenguang1998
3 | * @Date: 2024-07-28 22:00:00
4 | * @LastEditors: wuchenguang1998
5 | * @LastEditTime: 2024-07-28 23:00:00
6 | * @Description: 矩形元素,圆角属性适配元素放缩影响
7 | */
8 | import { fabric } from 'fabric';
9 |
10 | fabric.Rect = fabric.util.createClass(fabric.Rect, {
11 | type: 'rect',
12 | initialize: function (options) {
13 | options || (options = {});
14 | this.callSuper('initialize', options);
15 | },
16 | _render(ctx) {
17 | const roundValue = this.roundValue || 0;
18 | this.rx = (1 / this.scaleX) * roundValue;
19 | this.ry = (1 / this.scaleY) * roundValue;
20 | this.callSuper('_render', ctx);
21 | },
22 | });
23 |
24 | fabric.Rect.fromObject = function (object, callback) {
25 | return fabric.Object._fromObject('Rect', object, callback);
26 | };
27 |
28 | export default fabric.Rect;
29 |
--------------------------------------------------------------------------------
/packages/core/objects/ThinTailArrow.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: wuchenguang1998
3 | * @Date: 2024-05-10 10:46:10
4 | * @LastEditors: wuchenguang1998
5 | * @LastEditTime: 2024-05-10 22:08:51
6 | * @Description: 细尾箭头,支持控制条拖拽不变形
7 | */
8 | import { fabric } from 'fabric';
9 |
10 | fabric.ThinTailArrow = fabric.util.createClass(fabric.Line, {
11 | type: 'thinTailArrow',
12 | superType: 'drawing',
13 | initialize(points, options) {
14 | if (!points) {
15 | const { x1, x2, y1, y2 } = options;
16 | points = [x1, y1, x2, y2];
17 | }
18 | options = options || {};
19 | this.callSuper('initialize', points, options);
20 | },
21 | _render(ctx) {
22 | ctx.save();
23 | // 乘或除对应的scaleX(Y),抵消元素放缩造成的影响,使箭头不会变形
24 | ctx.scale(1 / this.scaleX, 1 / this.scaleY);
25 | const xDiff = (this.x2 - this.x1) * this.scaleX;
26 | const yDiff = (this.y2 - this.y1) * this.scaleY;
27 | ctx.translate(-xDiff / 2, -yDiff / 2);
28 | // 箭头方位角
29 | const angle = Math.atan2(yDiff, xDiff);
30 | ctx.rotate(angle);
31 | // 箭头总长(最小长度是20)
32 | let length = Math.hypot(xDiff, yDiff);
33 | length = length < 20 ? 20 : length;
34 | // 绘制箭头
35 | ctx.beginPath();
36 | ctx.moveTo(0, 0);
37 | ctx.lineTo(length - 18, -5);
38 | ctx.lineTo(length - 20, -12);
39 | ctx.lineTo(length, 0);
40 | ctx.lineTo(length - 20, 12);
41 | ctx.lineTo(length - 18, 5);
42 | ctx.lineTo(0, 0);
43 | ctx.lineWidth = this.strokeWidth;
44 | ctx.strokeStyle = this.stroke;
45 | ctx.fillStyle = this.fill;
46 | ctx.stroke();
47 | ctx.fill();
48 | ctx.restore();
49 | },
50 | });
51 |
52 | fabric.ThinTailArrow.fromObject = (options, callback) => {
53 | const { x1, x2, y1, y2 } = options;
54 | return callback(new fabric.ThinTailArrow([x1, y1, x2, y2], options));
55 | };
56 |
57 | export default fabric.ThinTailArrow;
58 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@kuaitu/core",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.ts",
6 | "scripts": {
7 | "build": "vite build",
8 | "test": "vitest"
9 | },
10 | "dependencies": {
11 | "@webtoon/psd": "^0.4.0",
12 | "events": "^3.3.0",
13 | "fabric-history": "^1.6.0",
14 | "fontfaceobserver": "^2.1.0",
15 | "hotkeys-js": "^3.8.8",
16 | "jsbarcode": "^3.11.6",
17 | "qr-code-styling": "1.6.0-rc.1",
18 | "qs": "^6.12.1",
19 | "tapable": "^2.2.1",
20 | "uuid": "^8.3.2"
21 | },
22 | "keywords": [],
23 | "author": "",
24 | "license": "ISC",
25 | "devDependencies": {
26 | "@types/jsdom": "^21.1.6",
27 | "@types/qs": "^6.9.15",
28 | "jsdom": "^24.0.0",
29 | "vitest": "^1.6.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/core/plugin.ts:
--------------------------------------------------------------------------------
1 | import Editor from './Editor';
2 | type IEditor = Editor;
3 |
4 | class FontPlugin {
5 | public canvas: fabric.Canvas;
6 | public editor: IEditor;
7 | // 插件名称
8 | static pluginName = 'FontPlugin';
9 | // 挂载API名称
10 | static apis = ['downFontByJSON'];
11 | // 发布事件
12 | static events = ['textEvent1', 'textEvent2'];
13 | // 快捷键 keyCode hotkeys-js
14 | public hotkeys: string[] = ['backspace', 'space'];
15 | // 私有属性
16 | repoSrc: string;
17 |
18 | constructor(canvas: fabric.Canvas, editor: IEditor, config: { repoSrc: string }) {
19 | // 初始化
20 | this.canvas = canvas;
21 | this.editor = editor;
22 | // 可插入外部配置
23 | this.repoSrc = config.repoSrc;
24 | }
25 |
26 | // 钩子函数 hookImportAfter/hookSaveBefore/hookSaveAfter Promise
27 | hookImportBefore(json: string) {
28 | return this.downFontByJSON(json);
29 | }
30 |
31 | // 挂载API方法
32 | downFontByJSON() {
33 | //
34 | }
35 |
36 | // 私有方法 + 发布事件
37 | _createFontCSS() {
38 | const params = [];
39 | this.editor.emit('textEvent1', params);
40 | }
41 |
42 | // 右键菜单
43 | contextMenu() {
44 | const selectedMode = this.editor.getSelectMode();
45 | if (selectedMode === SelectMode.ONE) {
46 | return [
47 | null, // 分割线
48 | {
49 | text: '翻转',
50 | hotkey: '❯',
51 | subitems: [
52 | {
53 | text: t('flip.x'),
54 | hotkey: '|',
55 | onclick: () => this.flip('X'),
56 | },
57 | {
58 | text: t('flip.y'),
59 | hotkey: '-',
60 | onclick: () => this.flip('Y'),
61 | },
62 | ],
63 | },
64 | ];
65 | }
66 | }
67 |
68 | // 快捷键
69 | hotkeyEvent(eventName: string, { type }: KeyboardEvent) {
70 | // eventName:hotkeys中的属性 backspace、space
71 | // type:keyUp keyDown
72 | // code:hotkeys-js Code
73 | if (eventName === 'backspace' && type === 'keydown') {
74 | this.del();
75 | }
76 | }
77 |
78 | // 注销
79 | destroy() {
80 | console.log('pluginDestroy');
81 | }
82 | }
83 |
84 | export default FontPlugin;
85 |
--------------------------------------------------------------------------------
/packages/core/plugin/CenterAlignPlugin.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 秦少卫
3 | * @Date: 2023-06-15 22:49:42
4 | * @LastEditors: bigFace2019 599069310@qq.com
5 | * @LastEditTime: 2024-11-03 20:39:43
6 | * @Description: 居中对齐插件
7 | */
8 |
9 | import { fabric } from 'fabric';
10 | import type { IEditor, IPluginTempl } from '@kuaitu/core';
11 |
12 | type IPlugin = Pick;
13 |
14 | declare module '@kuaitu/core' {
15 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
16 | interface IEditor extends IPlugin {}
17 | }
18 |
19 | class CenterAlignPlugin implements IPluginTempl {
20 | static pluginName = 'CenterAlignPlugin';
21 | static apis = ['centerH', 'center', 'position', 'centerV'];
22 | // public hotkeys: string[] = ['space'];
23 | constructor(public canvas: fabric.Canvas, public editor: IEditor) {}
24 |
25 | center(workspace: fabric.Rect, object: fabric.Object) {
26 | const center = workspace.getCenterPoint();
27 | return this.canvas._centerObject(object, center);
28 | }
29 |
30 | centerV(workspace: fabric.Rect, object: fabric.Object) {
31 | return this.canvas._centerObject(
32 | object,
33 | new fabric.Point(object.getCenterPoint().x, workspace.getCenterPoint().y)
34 | );
35 | }
36 |
37 | centerH(workspace: fabric.Rect, object: fabric.Object) {
38 | return this.canvas._centerObject(
39 | object,
40 | new fabric.Point(workspace.getCenterPoint().x, object.getCenterPoint().y)
41 | );
42 | }
43 |
44 | position(name: 'centerH' | 'center' | 'centerV') {
45 | const anignType = ['centerH', 'center', 'centerV'];
46 | const activeObject = this.canvas.getActiveObject();
47 | if (anignType.includes(name) && activeObject) {
48 | const defaultWorkspace = this.canvas.getObjects().find((item) => item.id === 'workspace');
49 | if (defaultWorkspace) {
50 | this[name](defaultWorkspace, activeObject);
51 | }
52 | this.canvas.renderAll();
53 | }
54 | }
55 |
56 | contextMenu() {
57 | const activeObject = this.canvas.getActiveObject();
58 | if (activeObject) {
59 | return [
60 | {
61 | text: '水平垂直居中',
62 | hotkey: '',
63 | disabled: false,
64 | onclick: () => this.position('center'),
65 | },
66 | ];
67 | }
68 | }
69 | destroy() {
70 | console.log('pluginDestroy');
71 | }
72 | }
73 |
74 | export default CenterAlignPlugin;
75 |
--------------------------------------------------------------------------------
/packages/core/plugin/DeleteHotKeyPlugin.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 秦少卫
3 | * @Date: 2023-06-20 12:57:35
4 | * @LastEditors: bigFace2019 599069310@qq.com
5 | * @LastEditTime: 2024-11-03 20:38:33
6 | * @Description: 删除快捷键
7 | */
8 |
9 | import { fabric } from 'fabric';
10 | import type { IEditor, IPluginTempl } from '@kuaitu/core';
11 |
12 | type IPlugin = Pick;
13 |
14 | declare module '@kuaitu/core' {
15 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
16 | interface IEditor extends IPlugin {}
17 | }
18 |
19 | class DeleteHotKeyPlugin implements IPluginTempl {
20 | static pluginName = 'DeleteHotKeyPlugin';
21 | static apis = ['del'];
22 | hotkeys: string[] = ['backspace'];
23 | constructor(public canvas: fabric.Canvas, public editor: IEditor) {}
24 |
25 | // 快捷键扩展回调
26 | hotkeyEvent(eventName: string, e: KeyboardEvent) {
27 | if (e.type === 'keydown' && eventName === 'backspace') {
28 | this.del();
29 | }
30 | }
31 |
32 | del() {
33 | const { canvas } = this;
34 | const activeObject = canvas.getActiveObjects();
35 | if (activeObject) {
36 | activeObject.map((item) => canvas.remove(item));
37 | canvas.requestRenderAll();
38 | canvas.discardActiveObject();
39 | }
40 | }
41 |
42 | contextMenu() {
43 | const activeObject = this.canvas.getActiveObject();
44 | if (activeObject) {
45 | return [
46 | null,
47 | { text: '删除', hotkey: 'Backspace', disabled: false, onclick: () => this.del() },
48 | ];
49 | }
50 | }
51 |
52 | destroy() {
53 | console.log('pluginDestroy');
54 | }
55 | }
56 |
57 | export default DeleteHotKeyPlugin;
58 |
--------------------------------------------------------------------------------
/packages/core/plugin/FlipPlugin.ts:
--------------------------------------------------------------------------------
1 | import { fabric } from 'fabric';
2 | import { SelectMode } from '../eventType';
3 | import type { IEditor, IPluginTempl } from '@kuaitu/core';
4 | import i18n from "@/language";
5 |
6 | type IPlugin = Pick;
7 |
8 | declare module '@kuaitu/core' {
9 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
10 | interface IEditor extends IPlugin {}
11 | }
12 |
13 | // import event from '@/utils/event/notifier';
14 |
15 | export default class FlipPlugin implements IPluginTempl {
16 | static pluginName = 'FlipPlugin';
17 | static apis = ['flip'];
18 | constructor(public canvas: fabric.Canvas, public editor: IEditor) {}
19 |
20 | flip(type: 'X' | 'Y') {
21 | const activeObject = this.canvas.getActiveObject();
22 | if (activeObject) {
23 | activeObject.set(`flip${type}`, !activeObject[`flip${type}`]).setCoords();
24 | this.canvas.requestRenderAll();
25 | }
26 | }
27 |
28 | contextMenu() {
29 | const selectedMode = this.editor.getSelectMode();
30 | if (selectedMode === SelectMode.ONE) {
31 | return [
32 | {
33 | text: '翻转',
34 | hotkey: '❯',
35 | subitems: [
36 | {
37 | text: i18n.global.t('flip.x'),
38 | hotkey: '|',
39 | onclick: () => this.flip('X'),
40 | },
41 | {
42 | text: i18n.global.t('flip.y'),
43 | hotkey: '-',
44 | onclick: () => this.flip('Y'),
45 | },
46 | ],
47 | },
48 | ];
49 | }
50 | }
51 |
52 | destroy() {
53 | console.log('pluginDestroy');
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/core/plugin/FreeDrawPlugin.ts:
--------------------------------------------------------------------------------
1 | import { fabric } from 'fabric';
2 | import { v4 as uuid } from 'uuid';
3 | import type { IEditor, IPluginTempl } from '@kuaitu/core';
4 |
5 | type IPlugin = Pick;
6 |
7 | declare module '@kuaitu/core' {
8 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
9 | interface IEditor extends IPlugin {}
10 | }
11 |
12 | type DrawOptions = {
13 | width: number;
14 | };
15 |
16 | export default class FreeDrawPlugin implements IPluginTempl {
17 | static pluginName = 'FreeDrawPlugin';
18 | static apis = ['startDraw', 'endDraw'];
19 | constructor(public canvas: fabric.Canvas, public editor: IEditor) {}
20 |
21 | _bindEvent() {
22 | this.canvas.on('path:created', this._createdHandler);
23 | }
24 |
25 | _unbindEvent() {
26 | this.canvas.off('path:created', this._createdHandler);
27 | }
28 |
29 | _createdHandler = (opt: any) => {
30 | opt.path.set('id', uuid());
31 | };
32 |
33 | startDraw(options: DrawOptions) {
34 | this.canvas.isDrawingMode = true;
35 | this.canvas.freeDrawingBrush = new fabric.PencilBrush(this.canvas);
36 | this.canvas.freeDrawingBrush.width = options.width;
37 | this._bindEvent();
38 | }
39 | endDraw() {
40 | if (this.canvas.isDrawingMode) {
41 | this.canvas.isDrawingMode = false;
42 | this._unbindEvent();
43 | return;
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/core/plugin/GroupPlugin.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 秦少卫
3 | * @Date: 2023-06-20 13:21:10
4 | * @LastEditors: 秦少卫
5 | * @LastEditTime: 2024-04-10 23:26:13
6 | * @Description: 组合拆分组合插件
7 | */
8 |
9 | import { fabric } from 'fabric';
10 | import { isGroup, isActiveSelection } from '../utils/utils';
11 | import { v4 as uuid } from 'uuid';
12 | import type { IEditor, IPluginTempl } from '@kuaitu/core';
13 |
14 | type IPlugin = Pick;
15 |
16 | declare module '@kuaitu/core' {
17 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
18 | interface IEditor extends IPlugin {}
19 | }
20 |
21 | class GroupPlugin implements IPluginTempl {
22 | static pluginName = 'GroupPlugin';
23 | static apis = ['unGroup', 'group'];
24 | constructor(public canvas: fabric.Canvas, public editor: IEditor) {}
25 |
26 | // 拆分组
27 | unGroup() {
28 | const activeObject = this.canvas.getActiveObject() as fabric.Group;
29 | if (!activeObject) return;
30 | // 先获取当前选中的对象,然后打散
31 | const activeObjectList = activeObject.getObjects();
32 | activeObject.toActiveSelection();
33 | for (const item of activeObjectList) {
34 | item.set('id', uuid());
35 | }
36 | this.canvas.discardActiveObject().renderAll();
37 | }
38 |
39 | group() {
40 | // 组合元素
41 | const activeObj = this.canvas.getActiveObject() as fabric.ActiveSelection;
42 | if (!activeObj) return;
43 | const activegroup = activeObj.toGroup();
44 | const objectsInGroup = activegroup.getObjects();
45 | activegroup.clone((newgroup: fabric.Group) => {
46 | newgroup.set('id', uuid());
47 | this.canvas.remove(activegroup);
48 | objectsInGroup.forEach((object) => {
49 | this.canvas.remove(object);
50 | });
51 | this.canvas.add(newgroup);
52 | this.canvas.setActiveObject(newgroup);
53 | });
54 | }
55 |
56 | contextMenu() {
57 | const activeObject = this.canvas.getActiveObject();
58 |
59 | if (isActiveSelection(activeObject)) {
60 | return [{ text: '组合', hotkey: 'Ctrl+V', disabled: false, onclick: () => this.group() }];
61 | }
62 |
63 | if (isGroup(activeObject)) {
64 | return [
65 | { text: '拆分组合', hotkey: 'Ctrl+V', disabled: false, onclick: () => this.unGroup() },
66 | ];
67 | }
68 | }
69 | destroy() {
70 | console.log('pluginDestroy');
71 | }
72 | }
73 |
74 | export default GroupPlugin;
75 |
--------------------------------------------------------------------------------
/packages/core/plugin/MiddleMousePlugin.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: George
3 | * @Date: 2024-10-29 11:11:11
4 | * @LastEditors: George
5 | * @LastEditTime: 2024-10-29 11:11:11
6 | * @Description: 鼠标中键点击事件插件
7 | */
8 |
9 | import { fabric } from 'fabric';
10 | import type { IEditor, IPluginTempl } from '@kuaitu/core';
11 |
12 | class MiddleMousePlugin implements IPluginTempl {
13 | static pluginName = 'MiddleMousePlugin';
14 | workspaceEl!: HTMLElement;
15 |
16 | constructor(public canvas: fabric.Canvas, public editor: IEditor) {
17 | this.init();
18 | }
19 |
20 | private init() {
21 | const workspaceEl = document.querySelector('#workspace') as HTMLElement;
22 | if (!workspaceEl) {
23 | throw new Error('element #workspace is missing, plz check!');
24 | }
25 | this.workspaceEl = workspaceEl;
26 | this.initListener();
27 | }
28 |
29 | private handleMouseUp = (e: MouseEvent) => e.button === 1 && this.canvas.fire('mouse:up', { e });
30 |
31 | private handleMouseDown = (e: MouseEvent) =>
32 | e.button === 1 && this.canvas.fire('mouse:down', { e });
33 |
34 | /**
35 | * @desc 初始化鼠标中键监听事件
36 | */
37 | private initListener() {
38 | this.workspaceEl.addEventListener('mouseup', this.handleMouseUp);
39 | this.workspaceEl.addEventListener('mousedown', this.handleMouseDown);
40 | }
41 |
42 | destroy() {
43 | this.workspaceEl.removeEventListener('mouseup', this.handleMouseUp);
44 | this.workspaceEl.removeEventListener('mousedown', this.handleMouseDown);
45 | console.log('pluginDestroy');
46 | }
47 | }
48 |
49 | export default MiddleMousePlugin;
50 |
--------------------------------------------------------------------------------
/packages/core/plugin/MoveHotKeyPlugin.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 秦少卫
3 | * @Date: 2023-06-20 12:52:09
4 | * @LastEditors: 秦少卫
5 | * @LastEditTime: 2024-04-10 17:32:31
6 | * @Description: 移动快捷键
7 | */
8 |
9 | import { fabric } from 'fabric';
10 | import type { IEditor, IPluginTempl } from '@kuaitu/core';
11 |
12 | class MoveHotKeyPlugin implements IPluginTempl {
13 | static pluginName = 'MoveHotKeyPlugin';
14 | hotkeys: string[] = ['left', 'right', 'down', 'up'];
15 | constructor(public canvas: fabric.Canvas, public editor: IEditor) {}
16 |
17 | // 快捷键扩展回调
18 | hotkeyEvent(eventName: string, e: KeyboardEvent) {
19 | if (e.type === 'keydown') {
20 | const { canvas } = this;
21 | const activeObject = canvas.getActiveObject();
22 | if (!activeObject) return;
23 | switch (eventName) {
24 | case 'left':
25 | if (activeObject.left === undefined) return;
26 | activeObject.set('left', activeObject.left - 1);
27 | break;
28 | case 'right':
29 | if (activeObject.left === undefined) return;
30 | activeObject.set('left', activeObject.left + 1);
31 | break;
32 | case 'down':
33 | if (activeObject.top === undefined) return;
34 | activeObject.set('top', activeObject.top + 1);
35 | break;
36 | case 'up':
37 | if (activeObject.top === undefined) return;
38 | activeObject.set('top', activeObject.top - 1);
39 | break;
40 | default:
41 | }
42 | canvas.renderAll();
43 | }
44 | }
45 |
46 | destroy() {
47 | console.log('pluginDestroy');
48 | }
49 | }
50 |
51 | export default MoveHotKeyPlugin;
52 |
--------------------------------------------------------------------------------
/packages/core/plugin/PathTextPlugin.ts:
--------------------------------------------------------------------------------
1 | import { fabric } from 'fabric';
2 | import { v4 as uuid } from 'uuid';
3 | import type { IEditor, IPluginTempl } from '@kuaitu/core';
4 |
5 | type IPlugin = Pick;
6 |
7 | declare module '@kuaitu/core' {
8 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
9 | interface IEditor extends IPlugin {}
10 | }
11 |
12 | type DrawOptions = {
13 | decimate: number;
14 | width: number;
15 | defaultText: string;
16 | color: string;
17 | lineColor: string;
18 | defaultFontSize: number;
19 | };
20 |
21 | export default class PathTextPlugin implements IPluginTempl {
22 | static pluginName = 'PathTextPlugin';
23 | static apis = ['startTextPathDraw', 'endTextPathDraw'];
24 | private options?: DrawOptions;
25 | constructor(public canvas: fabric.Canvas, public editor: IEditor) {}
26 |
27 | _beforeHandler = (opt: any) => {
28 | if (this.options == null) return;
29 | const path = opt.path as any;
30 | const getPathSegmentsInfo = (fabric.util as any).getPathSegmentsInfo;
31 | path.segmentsInfo = getPathSegmentsInfo(path.path);
32 | path.set({ stroke: this.options.lineColor });
33 | const text = this.options.defaultText;
34 | const fontSize = this.options.defaultFontSize;
35 | const textObject = new fabric.IText(text, {
36 | shadow: '',
37 | fontFamily: 'arial',
38 | fontSize: fontSize,
39 | top: path.top,
40 | left: path.left,
41 | fill: this.options.color,
42 | path: path,
43 | id: uuid(),
44 | // 路径文字元素禁止在画布上直接编辑
45 | editable: false,
46 | });
47 | this.canvas.add(textObject);
48 | };
49 | _createdHandler = (opt: any) => {
50 | this.canvas.remove(opt.path);
51 | };
52 | _bindEvent() {
53 | this.canvas.on('before:path:created', this._beforeHandler);
54 | this.canvas.on('path:created', this._createdHandler);
55 | }
56 | _unbindEvent() {
57 | this.canvas.off('before:path:created', this._beforeHandler);
58 | this.canvas.off('path:created', this._createdHandler);
59 | }
60 | startTextPathDraw(options: Partial = {}) {
61 | const defaultOptions = {
62 | decimate: 8,
63 | width: 2,
64 | defaultText: '诸事顺遂 万事大吉',
65 | color: '#000000',
66 | lineColor: '#000000',
67 | defaultFontSize: 20,
68 | };
69 | this.options = {
70 | ...defaultOptions,
71 | ...options,
72 | };
73 | this.canvas.isDrawingMode = true;
74 | const brush = (this.canvas.freeDrawingBrush = new fabric.PencilBrush(this.canvas));
75 | brush.decimate = this.options.decimate;
76 | brush.width = this.options.width;
77 | brush.color = this.options.color;
78 | this._bindEvent();
79 | }
80 | endTextPathDraw() {
81 | this.canvas.isDrawingMode = false;
82 | this._unbindEvent();
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/packages/core/plugin/PsdPlugin.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 秦少卫
3 | * @Date: 2024-05-27 16:09:29
4 | * @LastEditors: 秦少卫
5 | * @LastEditTime: 2024-06-08 18:31:24
6 | * @Description: PSD插件
7 | */
8 | import { fabric } from 'fabric';
9 | import { selectFiles } from '../utils/utils';
10 | import psdToJson from '../utils/psd';
11 | import Psd from '@webtoon/psd';
12 | import type { IEditor, IPluginTempl } from '@kuaitu/core';
13 |
14 | type IPlugin = Pick;
15 |
16 | declare module '@kuaitu/core' {
17 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
18 | interface IEditor extends IPlugin {}
19 | }
20 |
21 | class PsdPlugin implements IPluginTempl {
22 | static pluginName = 'PsdPlugin';
23 | static apis = ['insertPSD'];
24 | constructor(public canvas: fabric.Canvas, public editor: IEditor) {}
25 |
26 | insertPSD(callback?: () => void) {
27 | return new Promise((resolve, reject) => {
28 | selectFiles({ accept: '.psd' })
29 | .then((files) => {
30 | if (files && files.length > 0) {
31 | const file = files[0];
32 | const reader = new FileReader();
33 | reader.readAsText(file, 'UTF-8');
34 | reader.onload = async () => {
35 | const result = await file.arrayBuffer();
36 | // 解析PSD文件
37 | const psdFile = Psd.parse(result as ArrayBuffer);
38 | console.log(psdFile, '11111');
39 | const json = await psdToJson(psdFile);
40 | // 加载json
41 | this.loadJSON(json, callback);
42 | resolve('');
43 | };
44 | }
45 | })
46 | .catch(reject);
47 | });
48 | }
49 |
50 | loadJSON(json: string, callback?: () => void) {
51 | this.editor.loadJSON(json, callback);
52 | }
53 | }
54 |
55 | export default PsdPlugin;
56 |
--------------------------------------------------------------------------------
/packages/core/plugin/RulerPlugin.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 秦少卫
3 | * @Date: 2023-07-04 23:45:49
4 | * @LastEditors: 秦少卫
5 | * @LastEditTime: 2024-04-10 17:33:54
6 | * @Description: 标尺插件
7 | */
8 |
9 | import { fabric } from 'fabric';
10 | import type { IEditor, IPluginTempl } from '@kuaitu/core';
11 |
12 | type IPlugin = Pick<
13 | RulerPlugin,
14 | 'hideGuideline' | 'showGuideline' | 'rulerEnable' | 'rulerDisable'
15 | >;
16 |
17 | declare module '@kuaitu/core' {
18 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
19 | interface IEditor extends IPlugin {}
20 | }
21 |
22 | import initRuler from '../ruler';
23 |
24 | class RulerPlugin implements IPluginTempl {
25 | static pluginName = 'RulerPlugin';
26 | // static events = ['sizeChange'];
27 | static apis = ['hideGuideline', 'showGuideline', 'rulerEnable', 'rulerDisable'];
28 | ruler: any;
29 | constructor(public canvas: fabric.Canvas, public editor: IEditor) {
30 | this.init();
31 | }
32 |
33 | hookSaveBefore() {
34 | return new Promise((resolve) => {
35 | this.hideGuideline();
36 | resolve(true);
37 | });
38 | }
39 |
40 | hookSaveAfter() {
41 | return new Promise((resolve) => {
42 | this.showGuideline();
43 | resolve(true);
44 | });
45 | }
46 |
47 | init() {
48 | this.ruler = initRuler(this.canvas);
49 | }
50 |
51 | hideGuideline() {
52 | this.ruler.hideGuideline();
53 | }
54 |
55 | showGuideline() {
56 | this.ruler.showGuideline();
57 | }
58 |
59 | rulerEnable() {
60 | this.ruler.enable();
61 | }
62 |
63 | rulerDisable() {
64 | this.ruler.disable();
65 | }
66 |
67 | destroy() {
68 | console.log('pluginDestroy');
69 | }
70 | }
71 |
72 | export default RulerPlugin;
73 |
--------------------------------------------------------------------------------
/packages/core/ruler/index.ts:
--------------------------------------------------------------------------------
1 | import type { Canvas } from 'fabric/fabric-impl';
2 | import { fabric } from 'fabric';
3 | import CanvasRuler, { RulerOptions } from './ruler';
4 |
5 | function initRuler(canvas: Canvas, options?: RulerOptions) {
6 | const ruler = new CanvasRuler({
7 | canvas,
8 | ...options,
9 | });
10 |
11 | // 辅助线移动到画板外删除
12 | let workspace: fabric.Object | undefined = undefined;
13 |
14 | /**
15 | * 获取workspace
16 | */
17 | const getWorkspace = () => {
18 | workspace = canvas.getObjects().find((item) => item.id === 'workspace');
19 | };
20 |
21 | /**
22 | * 判断target是否在object矩形外
23 | * @param object
24 | * @param target
25 | * @returns
26 | */
27 | const isRectOut = (object: fabric.Object, target: fabric.GuideLine): boolean => {
28 | const { top, height, left, width } = object;
29 |
30 | if (top === undefined || height === undefined || left === undefined || width === undefined) {
31 | return false;
32 | }
33 |
34 | const targetRect = target.getBoundingRect(true, true);
35 | const {
36 | top: targetTop,
37 | height: targetHeight,
38 | left: targetLeft,
39 | width: targetWidth,
40 | } = targetRect;
41 |
42 | if (
43 | target.isHorizontal() &&
44 | (top > targetTop + 1 || top + height < targetTop + targetHeight - 1)
45 | ) {
46 | return true;
47 | } else if (
48 | !target.isHorizontal() &&
49 | (left > targetLeft + 1 || left + width < targetLeft + targetWidth - 1)
50 | ) {
51 | return true;
52 | }
53 |
54 | return false;
55 | };
56 |
57 | canvas.on('guideline:moving', (e) => {
58 | if (!workspace) {
59 | getWorkspace();
60 | return;
61 | }
62 | const { target } = e;
63 | if (isRectOut(workspace, target)) {
64 | target.moveCursor = 'not-allowed';
65 | }
66 | });
67 |
68 | canvas.on('guideline:mouseup', (e) => {
69 | if (!workspace) {
70 | getWorkspace();
71 | return;
72 | }
73 | const { target } = e;
74 | if (isRectOut(workspace, target)) {
75 | canvas.remove(target);
76 | canvas.setCursor(canvas.defaultCursor ?? '');
77 | }
78 | });
79 | return ruler;
80 | }
81 |
82 | export default initRuler;
83 |
--------------------------------------------------------------------------------
/packages/core/styles/contextMenu.css:
--------------------------------------------------------------------------------
1 | .context {
2 | display: inline-block;
3 | position: fixed;
4 | top: 0px;
5 | left: 0px;
6 | min-width: 270px;
7 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
8 | color: #fff;
9 | background: #262933;
10 | font-size: 9pt;
11 | border: 1px solid #333333;
12 | border-radius: 6px;
13 | box-shadow: 2px 2px 2px -1px rgba(0, 0, 0, 0.5);
14 | padding: 3px 0px;
15 | -webkit-touch-callout: none;
16 | -webkit-user-select: none;
17 | -khtml-user-select: none;
18 | -moz-user-select: none;
19 | -ms-user-select: none;
20 | user-select: none;
21 | z-index: 999;
22 | }
23 |
24 | .context .item {
25 | padding: 4px 19px;
26 | cursor: default;
27 | color: inherit;
28 | }
29 |
30 | .context .item:hover {
31 | background: #2777ff;
32 | }
33 |
34 | .context .item:hover .hotkey {
35 | color: #fff;
36 | }
37 |
38 | .context .disabled {
39 | color: #878b90;
40 | }
41 |
42 | .context .disabled:hover {
43 | background: inherit;
44 | }
45 |
46 | .context .disabled:hover .hotkey {
47 | color: #878b90;
48 | }
49 |
50 | .context .separator {
51 | margin: 4px 0px;
52 | height: 0;
53 | padding: 0;
54 | border-top: 1px solid #454545;
55 | }
56 |
57 | .hotkey {
58 | color: #878b90;
59 | float: right;
60 | }
61 |
--------------------------------------------------------------------------------
/packages/core/styles/resizePlugin.css:
--------------------------------------------------------------------------------
1 | .resize-bar {
2 | border-radius: 3px;
3 | background-color: rgba(160, 160, 160, 0.6);
4 | position: absolute;
5 | }
6 | .resize-bar:hover {
7 | background-color: #328cff;
8 | }
9 | .resize-bar.horizontal {
10 | width: 6px;
11 | height: 30px;
12 | }
13 | .resize-bar.vertical {
14 | width: 30px;
15 | height: 6px;
16 | }
17 | .resize-bar.active {
18 | background-color: #328cff;
19 | }
20 | #resize-left-bar,
21 | #resize-right-bar {
22 | cursor: ew-resize;
23 | }
24 | #resize-top-bar,
25 | #resize-bottom-bar {
26 | cursor: ns-resize;
27 | }
28 |
--------------------------------------------------------------------------------
/packages/core/vite.config.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Description:
3 | * @version:
4 | * @Author: June
5 | * @Date: 2023-04-24 00:25:39
6 | * @LastEditors: June 1601745371@qq.com
7 | * @LastEditTime: 2024-04-18 10:14:25
8 | */
9 | import { defineConfig } from 'vite';
10 | import eslintPlugin from 'vite-plugin-eslint'; //导入包
11 | import { resolve } from 'path';
12 |
13 | const config = () => {
14 | return {
15 | base: './',
16 | build: {
17 | lib: {
18 | entry: resolve(__dirname, './index.ts'),
19 | name: 'Kuaitu',
20 | fileName: 'index',
21 | },
22 | outDir: resolve(__dirname, '../../dist'),
23 | },
24 | plugins: [
25 | // 增加下面的配置项,这样在运行时就能检查eslint规范
26 | eslintPlugin({
27 | include: ['src/**/*.js', 'src/**/*.vue', 'src/*.js', 'src/*.vue'],
28 | }),
29 | ],
30 | };
31 | };
32 |
33 | export default defineConfig(config);
34 |
--------------------------------------------------------------------------------
/packages/core/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config';
2 |
3 | export default defineConfig((_configEnv) =>
4 | defineConfig({
5 | esbuild: { target: 'es2022' },
6 | optimizeDeps: {
7 | force: true,
8 | esbuildOptions: {
9 | target: 'es2022',
10 | },
11 | },
12 | test: {
13 | include: ['./__tests__/**/*.spec.ts'],
14 |
15 | deps: {
16 | interopDefault: true,
17 | },
18 | poolOptions: {
19 | threads: {
20 | singleThread: true,
21 | },
22 | },
23 | environment: 'jsdom',
24 | },
25 | })
26 | );
27 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'packages/*'
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikuaitu/vue-fabric-editor/6895eca27951c9ee1e86839b04a33029b1b0d7ef/public/favicon.ico
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
--------------------------------------------------------------------------------
/src/api/admin.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 秦少卫
3 | * @Date: 2024-06-09 13:04:51
4 | * @LastEditors: 秦少卫
5 | * @LastEditTime: 2024-06-09 15:00:49
6 | * @Description: 管理员API
7 | */
8 |
9 | import axios from 'axios';
10 | const baseURL = import.meta.env.APP_ADMINAPIHOST;
11 |
12 | const instance = axios.create({ baseURL });
13 |
14 | instance.interceptors.request.use(function (config) {
15 | const token = getToken();
16 | if (token) {
17 | config.headers['Authorization'] = `${token}`;
18 | }
19 | return config;
20 | });
21 |
22 | const tokenKey = 'AdminToken';
23 | export function getToken() {
24 | const token = localStorage.getItem(tokenKey);
25 | return token;
26 | }
27 |
28 | export function setToken(token: string) {
29 | localStorage.setItem(tokenKey, token);
30 | }
31 |
32 | // 新增模板
33 | export const createdTempl = (data: any) =>
34 | instance.post('/content-manager/collection-types/api::templ.templ', data);
35 |
36 | // 更新模板
37 | export const updataTempl = (id: any, data: any) =>
38 | instance.put(`/content-manager/collection-types/api::templ.templ/${id}`, data);
39 |
40 | // 上传图片
41 | export const uploadImg = (data: any) => instance.post('/upload', data);
42 |
43 | // 更新图片
44 | export const deleteImg = (id: string) => instance.delete('/upload/files/' + id);
45 |
46 | // 获取详情
47 | export const getTempl = (id: string) =>
48 | instance.get(`/content-manager/collection-types/api::templ.templ/${id}`);
49 |
--------------------------------------------------------------------------------
/src/api/material.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 秦少卫
3 | * @Date: 2024-04-24 14:07:06
4 | * @LastEditors: 秦少卫
5 | * @LastEditTime: 2024-10-07 17:06:16
6 | * @Description: 用户接口登录
7 | */
8 |
9 | import qs from 'qs';
10 | import axios from 'axios';
11 |
12 | import ApiClass from './apiClass';
13 |
14 | const baseURL = import.meta.env.APP_APIHOST;
15 |
16 | const instance = axios.create({ baseURL });
17 |
18 | // web详情
19 | export const getWebInfo = () => instance.get('/api/web-site?populate=*');
20 |
21 | // 获取素材分类
22 | export const getMaterialTypes = () => instance.get('/api/material-types');
23 |
24 | // 获取素材列表
25 | export const getMaterials = (data: any) => instance.get('/api/materials?' + data);
26 |
27 | // 获取根据分类获取素材
28 | export const getMaterialsByType = (data: any) =>
29 | instance.get('/api/materials?' + qs.stringify(data));
30 |
31 | // 获取字体分类分类
32 | export const getFontStyleTypes = () => instance.get('/api/font-style-types');
33 |
34 | // 获取字体素材离别奥列表
35 | export const getFontStyles = (data: any) => instance.get('/api/font-styles?' + data);
36 |
37 | // 获取根据分类获取字体样式列表
38 | export const getFontStyleListByType = (data: any) =>
39 | instance.get('/api/font-styles?' + qs.stringify(data));
40 |
41 | // 获取字体分类分类
42 | export const getTmplTypes = () => instance.get('/api/templ-types');
43 | // 获取模板列表
44 | export const getTmplList = (data: any) => instance.get('/api/templs?' + data);
45 |
46 | // 获取banner
47 | // export const getBannerList = (data: any) => instance.get('/api/banners?' + data);
48 |
49 | // 新版 API---------------------
50 | // 获取模板列表
51 | export const templsApi = new ApiClass('/api/templs');
52 | // 获取模板动态参数
53 | export const customDynamicsApi = new ApiClass('/api/custom/dynamics');
54 | // 获取模板渲染后的数据
55 | export const customRenderApi = new ApiClass('/api/custom/render');
56 | // 素材接口
57 | export const commonMaterialsApi = new ApiClass('/api/materials');
58 | // 素材分类
59 | export const commonMaterialsTypeApi = new ApiClass('/api/material-types');
60 | // 获取模板类型
61 | export const commonTmplTypeApi = new ApiClass('/api/templ-types');
62 | // 获取模板列表
63 | export const commonTmplApi = new ApiClass('/api/templs');
64 | // 获取组合元素分类
65 | export const commonFontGroupTypeApi = new ApiClass('/api/font-style-types');
66 | // 获取组合元素
67 | export const commonFontGroupApi = new ApiClass('/api/font-styles');
68 | // 获取字体列表
69 | export const commonFontApi = new ApiClass('/api/fonts');
70 | // 获取边框列表
71 | export const commonFontStyleApi = new ApiClass('/api/fontborders');
72 | // 获取画布大小
73 | export const commonSizeApi = new ApiClass('/api/sizes');
74 | // banner接口
75 | export const commonBannerApi = new ApiClass('/api/banners');
76 |
--------------------------------------------------------------------------------
/src/api/user.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 秦少卫
3 | * @Date: 2024-04-24 14:07:06
4 | * @LastEditors: 秦少卫
5 | * @LastEditTime: 2024-06-14 16:17:41
6 | * @Description: 用户接口登录
7 | */
8 |
9 | import axios from 'axios';
10 | const baseURL = import.meta.env.APP_APIHOST;
11 |
12 | const instance = axios.create({ baseURL });
13 |
14 | instance.interceptors.request.use(function (config) {
15 | const token = getToken();
16 | if (token) {
17 | config.headers['Authorization'] = `Bearer ${token}`;
18 | }
19 | return config;
20 | });
21 |
22 | const tokenKey = 'token';
23 | function getToken() {
24 | const token = localStorage.getItem(tokenKey);
25 | return token;
26 | }
27 |
28 | // 详情
29 | export const getUserInfo = (data: any) => instance.get('/api/users/me', data);
30 |
31 | // 登录
32 | export const login = (data: any) => instance.post('/api/auth/local', data);
33 |
34 | // 注册
35 | export const register = (data: any) => instance.post('/api/auth/local/register', data);
36 |
37 | // 登出
38 | export const logout = () => localStorage.setItem(tokenKey, '');
39 |
40 | // 自动登录
41 | export const autoLogin = (data: any) => instance.post('/api/custom/autoAuthUser', data);
42 |
43 | // 设置token
44 | export const setToken = (token: string) => localStorage.setItem(tokenKey, token);
45 |
46 | // 获取个人素材列表
47 | export const getFileList = (data: any) => instance.get('/api/user-materials?populate=*', data);
48 |
49 | // 上传素材
50 | export const uploadImg = (data: any) => instance.post('/api/upload', data);
51 |
52 | // 创建素材
53 | export const createdMaterial = (data: any) => instance.post('/api/user-materials', data);
54 |
55 | // 删除素材
56 | export const removeMaterial = (id: any) => instance.delete('/api/user-materials/' + id);
57 |
58 | // 创建模板
59 | export const createdTempl = (data: any) => instance.post('/api/user-templs', data);
60 |
61 | // 删除素材
62 | export const removeTempl = (data: any) => instance.delete(`/api/user-templs/${data}`);
63 |
64 | // 更新素材
65 | export const updataTempl = (id: any, data: any) => instance.put(`/api/user-templs/${id}`, data);
66 |
67 | // 查询素材列表
68 | export const getTmplList = (data: any) => instance.get(`/api/user-templs?${data}`);
69 |
70 | // 查询素材列表
71 | export const getTmplInfo = (data: any) => instance.get(`/api/user-templs/${data}`);
72 |
73 | // 获取用户树菜单
74 | export const getUserFileTypeTree = () => instance.get(`/api/user-templ/getUerFileTypeTree`);
75 |
76 | // 获取菜单树
77 | export const getFileTypeTree = (data: any) =>
78 | instance.get(`/api/custom/getUerFileTypeTree`, {
79 | params: data,
80 | });
81 |
82 | // 获取用户树菜单
83 | export const getUerFileTree = () => instance.get(`/api/user-templ/getUerFileTree`);
84 |
--------------------------------------------------------------------------------
/src/assets/filters/BlackWhite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikuaitu/vue-fabric-editor/6895eca27951c9ee1e86839b04a33029b1b0d7ef/src/assets/filters/BlackWhite.png
--------------------------------------------------------------------------------
/src/assets/filters/Brownie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikuaitu/vue-fabric-editor/6895eca27951c9ee1e86839b04a33029b1b0d7ef/src/assets/filters/Brownie.png
--------------------------------------------------------------------------------
/src/assets/filters/Invert.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikuaitu/vue-fabric-editor/6895eca27951c9ee1e86839b04a33029b1b0d7ef/src/assets/filters/Invert.png
--------------------------------------------------------------------------------
/src/assets/filters/Kodachrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikuaitu/vue-fabric-editor/6895eca27951c9ee1e86839b04a33029b1b0d7ef/src/assets/filters/Kodachrome.png
--------------------------------------------------------------------------------
/src/assets/filters/Polaroid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikuaitu/vue-fabric-editor/6895eca27951c9ee1e86839b04a33029b1b0d7ef/src/assets/filters/Polaroid.png
--------------------------------------------------------------------------------
/src/assets/filters/Sepia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikuaitu/vue-fabric-editor/6895eca27951c9ee1e86839b04a33029b1b0d7ef/src/assets/filters/Sepia.png
--------------------------------------------------------------------------------
/src/assets/filters/Vintage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikuaitu/vue-fabric-editor/6895eca27951c9ee1e86839b04a33029b1b0d7ef/src/assets/filters/Vintage.png
--------------------------------------------------------------------------------
/src/assets/filters/technicolor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikuaitu/vue-fabric-editor/6895eca27951c9ee1e86839b04a33029b1b0d7ef/src/assets/filters/technicolor.png
--------------------------------------------------------------------------------
/src/assets/fonts/cn/华康金刚黑.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikuaitu/vue-fabric-editor/6895eca27951c9ee1e86839b04a33029b1b0d7ef/src/assets/fonts/cn/华康金刚黑.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/cn/汉体.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikuaitu/vue-fabric-editor/6895eca27951c9ee1e86839b04a33029b1b0d7ef/src/assets/fonts/cn/汉体.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/font.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: '汉体';
3 | src: url('./cn/汉体.ttf');
4 | }
5 |
6 | @font-face {
7 | font-family: '华康金刚黑';
8 | src: url('./cn/华康金刚黑.ttf');
9 | }
10 |
--------------------------------------------------------------------------------
/src/assets/fonts/font.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 秦少卫
3 | * @Date: 2022-09-05 22:54:14
4 | * @LastEditors: 秦少卫
5 | * @LastEditTime: 2022-09-05 22:59:30
6 | * @Description: 字体文件列表
7 | */
8 |
9 | const cnList = [
10 | {
11 | name: '汉体',
12 | fontFamily: '汉体',
13 | },
14 | {
15 | name: '华康金刚黑',
16 | fontFamily: '华康金刚黑',
17 | },
18 | ];
19 |
20 | const enList = [];
21 |
22 | export default [...cnList, ...enList];
23 |
--------------------------------------------------------------------------------
/src/assets/icon/align/averageX.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/align/averageY.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/align/bottom.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/align/centerX.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/align/centerY.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/align/left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/align/right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/align/top.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/attribute/fontStyle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/attribute/fontWeight.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/attribute/linethrough.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/attribute/textAlignCenter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/attribute/textAlignJustitfy.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/attribute/textAlignLeft.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/attribute/textAlignRight.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/attribute/underline.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/barcode/center.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/barcode/left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/barcode/right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/bottom.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/centerAlign/center.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/centerAlign/centerX.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/centerAlign/centerY.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/centerx.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/centery.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/fileType.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikuaitu/vue-fabric-editor/6895eca27951c9ee1e86839b04a33029b1b0d7ef/src/assets/icon/fileType.png
--------------------------------------------------------------------------------
/src/assets/icon/flip/x.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/flip/y.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/group/group.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/group/unGroup.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/layer/circle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/layer/default.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/layer/down.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/layer/downTop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/layer/group.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/layer/iText.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/layer/image.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/layer/polygon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/layer/rect.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/layer/triangle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/layer/up.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/layer/upTop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/proIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikuaitu/vue-fabric-editor/6895eca27951c9ee1e86839b04a33029b1b0d7ef/src/assets/icon/proIcon.png
--------------------------------------------------------------------------------
/src/assets/icon/right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/sx.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/sy.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/tools/barCode.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/tools/circle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/tools/draw1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/tools/draw2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/tools/draw3.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/tools/draw4.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/tools/polygon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/tools/rect.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/tools/text.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/tools/triangle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/top.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/zoom/big.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon/zoom/small.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/admin.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 | {{ $t('admin.save') }}
19 | {{ $t('admin.setToken') }}
20 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/src/components/attributeId.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
数据
13 |
14 |
23 |
24 |
25 |
26 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
95 |
96 |
113 |
--------------------------------------------------------------------------------
/src/components/bgBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ $t('bgSeting.color') }}
7 |
8 |
13 |
14 |
15 |
16 |
17 |
18 | {{ $t('bgSeting.colorMacthing') }}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
34 |
35 |
36 |
37 |
97 |
98 |
118 |
--------------------------------------------------------------------------------
/src/components/centerAlign.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 | {{ $t('attrSeting.centerAlign.name') }}
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
27 |
28 |
29 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
51 |
63 |
--------------------------------------------------------------------------------
/src/components/clipImage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{ item.label }}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
86 |
91 |
--------------------------------------------------------------------------------
/src/components/clone.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 |
--------------------------------------------------------------------------------
/src/components/color-picker/comps/TabPanel.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
40 |
--------------------------------------------------------------------------------
/src/components/color-picker/comps/svg.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
28 |
29 |
--------------------------------------------------------------------------------
/src/components/color-picker/index.css:
--------------------------------------------------------------------------------
1 | /* ! tailwindcss v3.3.2 | MIT License | https://tailwindcss.com */
2 |
3 | /*
4 | Some configurations are extracted, and tailwindcss are not used in this project
5 | */
6 |
7 | *,
8 | ::before,
9 | ::after {
10 | --tw-border-spacing-x: 0;
11 | --tw-border-spacing-y: 0;
12 | --tw-translate-x: 0;
13 | --tw-translate-y: 0;
14 | --tw-rotate: 0;
15 | --tw-skew-x: 0;
16 | --tw-skew-y: 0;
17 | --tw-scale-x: 1;
18 | --tw-scale-y: 1;
19 | --tw-pan-x: ;
20 | --tw-pan-y: ;
21 | --tw-pinch-zoom: ;
22 | --tw-scroll-snap-strictness: proximity;
23 | --tw-gradient-from-position: ;
24 | --tw-gradient-via-position: ;
25 | --tw-gradient-to-position: ;
26 | --tw-ordinal: ;
27 | --tw-slashed-zero: ;
28 | --tw-numeric-figure: ;
29 | --tw-numeric-spacing: ;
30 | --tw-numeric-fraction: ;
31 | --tw-ring-inset: ;
32 | --tw-ring-offset-width: 0px;
33 | --tw-ring-offset-color: #fff;
34 | --tw-ring-color: rgb(59 130 246 / 0.5);
35 | --tw-ring-offset-shadow: 0 0 #0000;
36 | --tw-ring-shadow: 0 0 #0000;
37 | --tw-shadow: 0 0 #0000;
38 | --tw-shadow-colored: 0 0 #0000;
39 | --tw-blur: ;
40 | --tw-brightness: ;
41 | --tw-contrast: ;
42 | --tw-grayscale: ;
43 | --tw-hue-rotate: ;
44 | --tw-invert: ;
45 | --tw-saturate: ;
46 | --tw-sepia: ;
47 | --tw-drop-shadow: ;
48 | --tw-backdrop-blur: ;
49 | --tw-backdrop-brightness: ;
50 | --tw-backdrop-contrast: ;
51 | --tw-backdrop-grayscale: ;
52 | --tw-backdrop-hue-rotate: ;
53 | --tw-backdrop-invert: ;
54 | --tw-backdrop-opacity: ;
55 | --tw-backdrop-saturate: ;
56 | --tw-backdrop-sepia: ;
57 | }
58 |
59 | ::backdrop {
60 | --tw-border-spacing-x: 0;
61 | --tw-border-spacing-y: 0;
62 | --tw-translate-x: 0;
63 | --tw-translate-y: 0;
64 | --tw-rotate: 0;
65 | --tw-skew-x: 0;
66 | --tw-skew-y: 0;
67 | --tw-scale-x: 1;
68 | --tw-scale-y: 1;
69 | --tw-pan-x: ;
70 | --tw-pan-y: ;
71 | --tw-pinch-zoom: ;
72 | --tw-scroll-snap-strictness: proximity;
73 | --tw-gradient-from-position: ;
74 | --tw-gradient-via-position: ;
75 | --tw-gradient-to-position: ;
76 | --tw-ordinal: ;
77 | --tw-slashed-zero: ;
78 | --tw-numeric-figure: ;
79 | --tw-numeric-spacing: ;
80 | --tw-numeric-fraction: ;
81 | --tw-ring-inset: ;
82 | --tw-ring-offset-width: 0px;
83 | --tw-ring-offset-color: #fff;
84 | --tw-ring-color: rgb(59 130 246 / 0.5);
85 | --tw-ring-offset-shadow: 0 0 #0000;
86 | --tw-ring-shadow: 0 0 #0000;
87 | --tw-shadow: 0 0 #0000;
88 | --tw-shadow-colored: 0 0 #0000;
89 | --tw-blur: ;
90 | --tw-brightness: ;
91 | --tw-contrast: ;
92 | --tw-grayscale: ;
93 | --tw-hue-rotate: ;
94 | --tw-invert: ;
95 | --tw-saturate: ;
96 | --tw-sepia: ;
97 | --tw-drop-shadow: ;
98 | --tw-backdrop-blur: ;
99 | --tw-backdrop-brightness: ;
100 | --tw-backdrop-contrast: ;
101 | --tw-backdrop-grayscale: ;
102 | --tw-backdrop-hue-rotate: ;
103 | --tw-backdrop-invert: ;
104 | --tw-backdrop-opacity: ;
105 | --tw-backdrop-saturate: ;
106 | --tw-backdrop-sepia: ;
107 | }
108 |
--------------------------------------------------------------------------------
/src/components/color-picker/utils/helper.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: ShawnPhang
3 | * @Date: 2023-04-26 11:30:10
4 | * @Description:
5 | * @LastEditors: ShawnPhang
6 | * @LastEditTime: 2023-11-28 11:03:14
7 | */
8 | export const parseBackgroundValue = (value: string): string => {
9 | if (value.startsWith('#')) return '纯色';
10 | if (value.startsWith('linear-gradient')) return '渐变';
11 | return '图案';
12 | };
13 |
14 | interface Stop {
15 | color: string;
16 | offset: number;
17 | }
18 |
19 | export const toGradientString = (angle: number, stops: Stop[]) => {
20 | const s: string[] = [];
21 | stops.forEach((stop) => {
22 | s.push(`${stop.color} ${stop.offset * 100}%`);
23 | });
24 | return `linear-gradient(${angle}deg, ${s.join(',')})`;
25 | };
26 |
27 | /**
28 | * 显示全局提示
29 | * @param content
30 | * @param tooltipVisible
31 | * @returns
32 | */
33 | export function toolTip(content: string) {
34 | const tooltip = drawTooltip(content);
35 | document.body.appendChild(tooltip);
36 | setTimeout(() => tooltip?.parentNode?.removeChild(tooltip), 2000);
37 | }
38 |
39 | function drawTooltip(content: string, tooltipVisible = true) {
40 | const tooltip: any = document.createElement('div');
41 | tooltip.id = 'color-pipette-tooltip-container';
42 | tooltip.innerHTML = content;
43 | tooltip.style = `
44 | position: fixed;
45 | left: 50%;
46 | top: 9%;
47 | z-index: 10002;
48 | display: ${tooltipVisible ? 'flex' : 'none'};
49 | align-items: center;
50 | background-color: rgba(0,0,0,0.4);
51 | padding: 6px 12px;
52 | border-radius: 4px;
53 | color: #fff;
54 | font-size: 18px;
55 | pointer-events: none;
56 | `;
57 | return tooltip;
58 | }
59 |
--------------------------------------------------------------------------------
/src/components/color-picker/utils/moveable.ts:
--------------------------------------------------------------------------------
1 | import { toNumber } from './tool';
2 |
3 | interface Position {
4 | x: number;
5 | y: number;
6 | }
7 |
8 | interface RegisterMoveablePanelOptions {
9 | wrapEl?: HTMLElement;
10 | onmousedown?(position: Position, event: MouseEvent): void;
11 | onmousemove?(position: Position, event: MouseEvent): void;
12 | onmouseup?(position: Position, event: MouseEvent): void;
13 | }
14 |
15 | export const registerMoveableElement = (
16 | el: HTMLElement,
17 | { onmousedown, onmousemove, onmouseup }: RegisterMoveablePanelOptions = {}
18 | ) => {
19 | let elRect = el.getBoundingClientRect();
20 | const position = { x: 0, y: 0 };
21 |
22 | const update = (event: MouseEvent) => {
23 | let dx = event.pageX - elRect.x;
24 | let dy = event.pageY - elRect.y;
25 |
26 | if (dx < 0) dx = 0;
27 | if (dx > elRect.width) dx = elRect.width;
28 | if (dy < 0) dy = 0;
29 | if (dy > elRect.height) dy = elRect.height;
30 |
31 | position.x = toNumber(dx / elRect.width, { decimal: 2 });
32 | position.y = toNumber(dy / elRect.height, { decimal: 2 });
33 | };
34 |
35 | const _onmousemove = (event: MouseEvent) => {
36 | update(event);
37 |
38 | if (onmousemove) {
39 | onmousemove(position, event);
40 | }
41 | };
42 |
43 | const _onmouseup = (event: MouseEvent) => {
44 | document.removeEventListener('mousemove', _onmousemove);
45 | document.removeEventListener('mouseup', _onmouseup);
46 |
47 | if (onmouseup) {
48 | onmouseup(position, event);
49 | }
50 | };
51 |
52 | const _onmousedown = (event: MouseEvent) => {
53 | // elRect 可能不准确,这里更新一下
54 | elRect = el.getBoundingClientRect();
55 |
56 | update(event);
57 |
58 | document.addEventListener('mousemove', _onmousemove);
59 | document.addEventListener('mouseup', _onmouseup);
60 |
61 | if (onmousedown) {
62 | onmousedown(position, event);
63 | }
64 | };
65 |
66 | el.addEventListener('mousedown', _onmousedown);
67 |
68 | return {
69 | destroy() {
70 | el.removeEventListener('mousedown', _onmousedown);
71 | },
72 | };
73 | };
74 |
--------------------------------------------------------------------------------
/src/components/color-picker/utils/tool.ts:
--------------------------------------------------------------------------------
1 | export const toNumber = (n: number, { decimal = 0 } = {}) => {
2 | if (decimal > 0) {
3 | return Number(n.toFixed(decimal));
4 | }
5 | return Math.round(n);
6 | };
7 |
--------------------------------------------------------------------------------
/src/components/common/modalSzie.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 | {{ $t('importFiles.createDesign.customSize') }}
13 |
14 |
27 |
28 |
29 | {{ $t('importFiles.createDesign.systemSize') }}
30 |
31 |
32 |
40 |
41 |
42 |
43 | |
44 |
45 |
46 |
47 |
48 |
93 |
101 |
--------------------------------------------------------------------------------
/src/components/common/searchType.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
18 |
19 |
24 |
34 |
35 |
36 |
37 |
106 |
122 |
--------------------------------------------------------------------------------
/src/components/del.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
25 |
--------------------------------------------------------------------------------
/src/components/dragMode.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 | Drag
14 |
15 |
16 |
17 |
18 |
19 |
42 |
49 |
--------------------------------------------------------------------------------
/src/components/edit.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/components/flip.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
20 |
21 |
22 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
45 |
46 |
59 |
--------------------------------------------------------------------------------
/src/components/group.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
17 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
43 |
52 |
--------------------------------------------------------------------------------
/src/components/hide.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
44 |
--------------------------------------------------------------------------------
/src/components/history.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
27 |
28 |
29 |
30 |
51 |
52 |
63 |
64 |
69 |
--------------------------------------------------------------------------------
/src/components/importJSON.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
34 |
35 |
36 |
74 |
82 |
--------------------------------------------------------------------------------
/src/components/inputNumber/index.ts:
--------------------------------------------------------------------------------
1 | import InputNumber from './inputNumber.vue';
2 |
3 | export default InputNumber;
4 |
--------------------------------------------------------------------------------
/src/components/lang.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
15 |
16 |
17 |
18 | {{ lang.langName }}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/components/lock.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
38 |
39 |
55 |
--------------------------------------------------------------------------------
/src/components/logo.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
15 |
16 |
17 |
37 |
65 |
--------------------------------------------------------------------------------
/src/components/myMaterial/index.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 请先登录
21 |
22 |
23 |
43 |
44 |
50 |
--------------------------------------------------------------------------------
/src/components/previewCurrent.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
13 |
14 |
15 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/components/replaceImg.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
67 |
72 |
--------------------------------------------------------------------------------
/src/components/setSize.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 | {{ $t('bgSeting.size') }}
14 |
15 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
70 |
71 |
83 |
--------------------------------------------------------------------------------
/src/components/svgIcon/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 秦少卫
3 | * @Date: 2022-04-20 17:13:36
4 | * @LastEditors: 秦少卫
5 | * @LastEditTime: 2022-04-20 17:19:46
6 | * @Description: file content
7 | */
8 |
9 | import svgIcon from './index.vue';
10 |
11 | export default {
12 | install(Vue) {
13 | Vue.component(svgIcon.name, svgIcon);
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/src/components/svgIcon/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
36 |
37 |
38 |
39 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/components/workspaceMask.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
开启背景蒙版
4 |
5 |
6 |
7 | 开启
8 |
9 |
10 | 关闭
11 |
12 |
13 |
14 |
15 |
16 |
30 |
31 |
38 |
--------------------------------------------------------------------------------
/src/components/zoom.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
43 |
56 |
--------------------------------------------------------------------------------
/src/config/constants/app.ts:
--------------------------------------------------------------------------------
1 | export const LANG = 'lang'; // 多语言key
2 |
--------------------------------------------------------------------------------
/src/hooks/useAdmin.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 秦少卫
3 | * @Date: 2024-06-09 13:02:18
4 | * @LastEditors: 秦少卫
5 | * @LastEditTime: 2024-06-10 20:21:41
6 | * @Description: 管理面板管理
7 | */
8 |
9 | import { updataTempl, uploadImg, deleteImg, getTempl, createdTempl } from '@/api/admin';
10 | import { Spin } from 'view-ui-plus';
11 |
12 | import { useRouter } from 'vue-router';
13 |
14 | export default function useMaterial() {
15 | const canvasEditor = inject('canvasEditor');
16 | const router = useRouter();
17 | // 画布转图片
18 | const uploadFileToInfo = async () => {
19 | const dataURLtoFile = (dataurl, filename) => {
20 | var arr = dataurl.split(','),
21 | mime = arr[0].match(/:(.*?);/)[1],
22 | bstr = atob(arr[1]),
23 | n = bstr.length,
24 | u8arr = new Uint8Array(n);
25 | while (n--) {
26 | u8arr[n] = bstr.charCodeAt(n);
27 | }
28 | return new File([u8arr], filename, { type: mime });
29 | };
30 |
31 | const upload = (base64) => {
32 | const file = dataURLtoFile(base64, '123.png');
33 | const formData = new FormData();
34 | const time = new Date();
35 | formData.append('files', file, `${time.getTime()}`);
36 | return uploadImg(formData)
37 | .then((res) => {
38 | const [info] = res.data;
39 | return info;
40 | })
41 | .catch((err) => {
42 | console.log(err);
43 | });
44 | };
45 | const base64 = await canvasEditor.preview();
46 | // 上传图片
47 | const fileInfo = await upload(base64);
48 | return fileInfo.id;
49 | };
50 |
51 | // 更新详情
52 | const updataTemplHander = async (id) => {
53 | Spin.show();
54 | try {
55 | const { data: info } = await getTempl(id);
56 | const newImgId = await uploadFileToInfo();
57 | const json = canvasEditor.getJson();
58 | await updataTempl(info.id, {
59 | ...info,
60 | img: newImgId,
61 | json,
62 | });
63 | deleteImg(info.img.id);
64 | } catch (error) {
65 | console.log(error);
66 | }
67 |
68 | Spin.hide();
69 | };
70 |
71 | const createdTemplHander = async (name) => {
72 | Spin.show();
73 | try {
74 | const newImgId = await uploadFileToInfo();
75 | const json = canvasEditor.getJson();
76 | const res = await createdTempl({
77 | name,
78 | img: newImgId,
79 | json,
80 | });
81 | router.replace('/?tempId=' + res.data.id + '&admin=true');
82 | } catch (error) {
83 | console.log(error);
84 | }
85 |
86 | Spin.hide();
87 | };
88 | return {
89 | updataTemplHander,
90 | createdTemplHander,
91 | };
92 | }
93 |
--------------------------------------------------------------------------------
/src/hooks/useCalculate.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion: useCalculate
3 | * @version:
4 | * @Author: wuchenguang1998
5 | * @Date: 2024-05-18 15:42:17
6 | * @LastEditors: wuchenguang1998
7 | * @LastEditTime: 2024-05-18 17:28:34
8 | */
9 |
10 | export default function useCalculate() {
11 | const canvasEditor = inject('canvasEditor');
12 |
13 | // 获取画布的DOMRect对象
14 | const getCanvasBound = () => canvasEditor.canvas.getSelectionElement().getBoundingClientRect();
15 |
16 | // 判断拖拽结束的坐标是否在画布外
17 | const isOutsideCanvas = (x, y) => {
18 | const { left, right, top, bottom } = getCanvasBound();
19 | return x < left || x > right || y < top || y > bottom;
20 | };
21 |
22 | return {
23 | getCanvasBound,
24 | isOutsideCanvas,
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/src/hooks/useFileType.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: 秦少卫
3 | * @Date: 2024-05-29 15:13:58
4 | * @LastEditors: 秦少卫
5 | * @LastEditTime: 2024-05-29 15:32:49
6 | * @Description: 文件夹分页
7 | */
8 |
9 | import {
10 | createFileType as createFileTypeApi,
11 | getFileTypeList as getFileTypeListApi,
12 | } from '@/api/user';
13 | import { Spin } from 'view-ui-plus';
14 | export default function useFileType() {
15 | const createFileType = async (fileTypeName, parentId = '') => {
16 | Spin.show();
17 | const res = await createFileTypeApi({
18 | data: {
19 | name: fileTypeName,
20 | parentId,
21 | },
22 | });
23 | Spin.hide();
24 | return res;
25 | };
26 |
27 | const getFileTypeList = async (params) => {
28 | Spin.show();
29 | const res = await getFileTypeListApi({
30 | data: params,
31 | });
32 | Spin.hide();
33 | return res;
34 | };
35 |
36 | return {
37 | createFileType,
38 | getFileTypeList,
39 | };
40 | }
41 |
--------------------------------------------------------------------------------
/src/hooks/useSelectListen.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion: useSelectListen
3 | * @version:
4 | * @Author: wuchenguang1998
5 | * @Date: 2024-05-04 14:36:49
6 | * @LastEditors: 秦少卫
7 | * @LastEditTime: 2024-07-06 12:15:19
8 | */
9 | import Editor, { EventType } from '@kuaitu/core';
10 | import { get } from 'lodash-es';
11 |
12 | const { SelectEvent, SelectMode } = EventType;
13 |
14 | export interface Selector {
15 | mSelectMode: (typeof SelectMode)[keyof typeof SelectMode];
16 | mSelectOneType: string | undefined;
17 | mSelectId: string | undefined;
18 | mSelectIds: (string | undefined)[];
19 | mSelectActive: unknown[];
20 | }
21 |
22 | export default function useSelectListen(canvasEditor: Editor) {
23 | const state = reactive({
24 | mSelectMode: SelectMode.EMPTY,
25 | mSelectOneType: '',
26 | mSelectId: '', // 选择id
27 | mSelectIds: [], // 选择id
28 | mSelectActive: [],
29 | });
30 |
31 | const selectOne = (e: [fabric.Object]) => {
32 | state.mSelectMode = SelectMode.ONE;
33 | state.mSelectActive = e;
34 | if (e[0] && get(e[0], 'clip')) {
35 | selectCancel();
36 | // state.mSelectId = get(e[0], 'targetId');
37 | // state.mSelectOneType = get(e[0], 'targetType');
38 | // state.mSelectIds = e.map((item) => get(item, 'targetId'));
39 | return;
40 | }
41 | if (e[0]) {
42 | state.mSelectId = e[0].id;
43 | state.mSelectOneType = e[0].type;
44 | state.mSelectIds = e.map((item) => item.id);
45 | }
46 | };
47 |
48 | const selectMulti = (e: fabric.Object[]) => {
49 | state.mSelectMode = SelectMode.MULTI;
50 | state.mSelectId = '';
51 | state.mSelectIds = e.map((item) => item.id);
52 | };
53 |
54 | const selectCancel = () => {
55 | state.mSelectId = '';
56 | state.mSelectIds = [];
57 | state.mSelectMode = SelectMode.EMPTY;
58 | state.mSelectOneType = '';
59 | };
60 |
61 | onMounted(() => {
62 | canvasEditor.on(SelectEvent.ONE, selectOne);
63 | canvasEditor.on(SelectEvent.MULTI, selectMulti);
64 | canvasEditor.on(SelectEvent.CANCEL, selectCancel);
65 | });
66 |
67 | onBeforeMount(() => {
68 | canvasEditor.off(SelectEvent.ONE, selectOne);
69 | canvasEditor.off(SelectEvent.MULTI, selectMulti);
70 | canvasEditor.off(SelectEvent.CANCEL, selectCancel);
71 | });
72 |
73 | return {
74 | mixinState: state,
75 | };
76 | }
77 |
--------------------------------------------------------------------------------
/src/language/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: June
3 | * @Description:
4 | * @Date: 2023-10-29 12:18:14
5 | * @LastEditors: June
6 | * @LastEditTime: 2023-11-01 12:01:24
7 | */
8 | import { createI18n } from 'vue-i18n';
9 | import zh from 'view-ui-plus/dist/locale/zh-CN';
10 | import en from 'view-ui-plus/dist/locale/en-US'; //新版本把'iview'改成'view-design'
11 | import US from './en.json';
12 | import CN from './zh.json';
13 | import { getLocal, setLocal } from '@/utils/local';
14 | import { LANG } from '@/config/constants/app';
15 |
16 | const messages = {
17 | en: Object.assign(US, en), //将自己的英文包和iview提供的结合
18 | zh: Object.assign(CN, zh), //将自己的中文包和iview提供的结合
19 | };
20 |
21 | function getLocalLang() {
22 | let localLang = getLocal(LANG);
23 | if (!localLang) {
24 | let defaultLang = navigator.language;
25 | if (defaultLang) {
26 | // eslint-disable-next-line prefer-destructuring
27 | defaultLang = defaultLang.split('-')[0];
28 | // eslint-disable-next-line prefer-destructuring
29 | localLang = defaultLang.split('-')[0];
30 | }
31 | setLocal(LANG, defaultLang);
32 | }
33 | return localLang;
34 | }
35 | const lang = getLocalLang();
36 |
37 | const i18n = createI18n({
38 | allowComposition: true,
39 | globalInjection: true,
40 | legacy: false,
41 | locale: lang,
42 | messages,
43 | });
44 |
45 | export default i18n;
46 | export const t = (key: any) => {
47 | return i18n.global.t(key);
48 | };
49 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue';
2 | import App from './App.vue';
3 | import router from './router';
4 | import ViewUiPlus from 'view-ui-plus';
5 | import 'view-ui-plus/dist/styles/viewuiplus.css';
6 | import './styles/index.less';
7 | import VueLazyLoad from 'vue3-lazyload';
8 | // 自定义字体文件
9 | import '@/assets/fonts/font.css';
10 |
11 | import { VueMasonryPlugin } from 'vue-masonry';
12 |
13 | import i18n from './language/index';
14 |
15 | async function bootstrap() {
16 | const app = createApp(App);
17 | app.use(VueMasonryPlugin);
18 | app.use(router);
19 | app.use(i18n);
20 | app.use(VueLazyLoad, {});
21 | app.use(ViewUiPlus);
22 | await router.isReady();
23 | app.mount('#app');
24 | }
25 | bootstrap();
26 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHashHistory } from 'vue-router';
2 | import routes from './routes';
3 |
4 | export default createRouter({
5 | routes,
6 | history: createWebHashHistory(),
7 | scrollBehavior() {
8 | return { top: 0 };
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/src/router/routes.ts:
--------------------------------------------------------------------------------
1 | import type { RouteRecordRaw } from 'vue-router';
2 |
3 | import { setToken, autoLogin, logout } from '@/api/user';
4 |
5 | const routes: RouteRecordRaw[] = [
6 | {
7 | path: '/',
8 | beforeEnter: async (to) => {
9 | // 自动登录功能
10 | if (to.query.username && to.query.key) {
11 | const res = await autoLogin({
12 | username: to.query.username,
13 | projectid: to.query.projectid,
14 | key: to.query.key,
15 | });
16 | if (res.data.jwt) {
17 | setToken(res.data.jwt);
18 | } else {
19 | logout();
20 | alert('签名失败');
21 | window.location.href = '/';
22 | }
23 | }
24 | return true;
25 | },
26 | component: () => import('@/views/home/index.vue'),
27 | },
28 | {
29 | path: '/template',
30 | component: () => import('@/views/template/index.vue'),
31 | },
32 | ];
33 |
34 | export default routes;
35 |
--------------------------------------------------------------------------------
/src/styles/index.less:
--------------------------------------------------------------------------------
1 | @import '~view-ui-plus/src/styles/index.less';
2 | @import './resetViewUi.less';
3 | // view-ui iconfont (404问题 https://github.com/view-design/ViewUIPlus/issues/212)
4 | @ionicons-font-path: '~view-ui-plus/src/styles/common/iconfont/fonts';
5 | // @primary-color: #8c0776;
6 |
7 | div.attr-item-box {
8 | // padding-bottom: 20px;
9 | h3 {
10 | padding-bottom: 10px;
11 | }
12 |
13 | .ivu-tooltip {
14 | flex: 1;
15 | }
16 | .ivu-tooltip-rel {
17 | width: 100%;
18 | }
19 |
20 | .bg-item {
21 | width: 100%;
22 | // margin-bottom: 5px;
23 | padding: 5px;
24 | display: flex;
25 | flex: 1;
26 | justify-content: space-between;
27 | border-radius: 5px;
28 | background: #f6f7f9;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/styles/resetViewUi.less:
--------------------------------------------------------------------------------
1 | // viewUi中buttonGroup下几个button的focus状态下样式存在问题,后边的会覆盖前边的box-shadow,这里给focus元素添加z-index.
2 | .ivu-btn:focus {
3 | z-index: 2;
4 | -webkit-box-shadow: 0 0 0 2px rgba(45, 140, 240, 0.2);
5 | box-shadow: 0 0 0 2px rgba(45, 140, 240, 0.2);
6 | }
7 | .preview-modal-wrap {
8 | overflow: hidden;
9 | .ivu-modal-body {
10 | display: flex;
11 | padding: 0;
12 | max-height: calc(100vh - 51px);
13 | //滚动条移动上去才展示,移开不展示滚动条
14 | overflow: overlay;
15 | &::-webkit-scrollbar {
16 | width: 4px;
17 | background-color: #efeae6;
18 | }
19 | &:hover ::-webkit-scrollbar-track-piece {
20 | /*鼠标移动上去再显示滚动条*/
21 | background-color: #fff;
22 | /* 滚动条的背景颜色 */
23 | border-radius: 6px;
24 | /* 滚动条的圆角宽度 */
25 | }
26 | &:hover::-webkit-scrollbar-thumb:hover {
27 | background-color: #c1c1c1;
28 | }
29 | &:hover::-webkit-scrollbar-thumb:vertical {
30 | background-color: #c1c1c1;
31 | border-radius: 6px;
32 | outline: 2px solid #c1c1c1;
33 | outline-offset: -2px;
34 | border: 2px solid #c1c1c1;
35 | }
36 | }
37 | ivu-modal-content {
38 | border-radius: 6px 6px 0 0;
39 | }
40 | .ivu-modal {
41 | top: 0;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/styles/variable.less:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikuaitu/vue-fabric-editor/6895eca27951c9ee1e86839b04a33029b1b0d7ef/src/styles/variable.less
--------------------------------------------------------------------------------
/src/utils/local.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * get localStorage 获取本地存储
3 | * @param { String } key
4 | */
5 | export function getLocal(key: string) {
6 | if (!key) throw new Error('key is empty');
7 | const value = localStorage.getItem(key);
8 | return value ? JSON.parse(value) : null;
9 | }
10 |
11 | /**
12 | * set localStorage 设置本地存储
13 | * @param { String } key
14 | * @param value
15 | */
16 | export function setLocal(key: string, value: unknown) {
17 | if (!key) throw new Error('key is empty');
18 | if (!value) return;
19 | return localStorage.setItem(key, JSON.stringify(value));
20 | }
21 |
22 | /**
23 | * remove localStorage 移除某个本地存储
24 | * @param { String } key
25 | */
26 | export function removeLocal(key: string) {
27 | if (!key) throw new Error('key is empty');
28 | return localStorage.removeItem(key);
29 | }
30 |
31 | /**
32 | * clear localStorage 清除本地存储
33 | */
34 | export function clearLocal() {
35 | return localStorage.clear();
36 | }
37 |
--------------------------------------------------------------------------------
/src/utils/math.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 获取多边形顶点坐标
3 | * @param edges 变数
4 | * @param radius 半径
5 | * @returns 坐标数组
6 | */
7 | const getPolygonVertices = (edges: number, radius: number) => {
8 | const vertices = [];
9 | const interiorAngle = (Math.PI * 2) / edges;
10 | let rotationAdjustment = -Math.PI / 2;
11 | if (edges % 2 === 0) {
12 | rotationAdjustment += interiorAngle / 2;
13 | }
14 | for (let i = 0; i < edges; i++) {
15 | // 画圆取顶点坐标
16 | const rad = i * interiorAngle + rotationAdjustment;
17 | vertices.push({
18 | x: Math.cos(rad) * radius,
19 | y: Math.sin(rad) * radius,
20 | });
21 | }
22 | return vertices;
23 | };
24 |
25 | export { getPolygonVertices };
26 |
--------------------------------------------------------------------------------
/src/views/home/components/top/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
69 |
70 |
81 |
--------------------------------------------------------------------------------
/src/views/template/components/banner.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
27 |
28 |
45 |
63 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "outDir": "build",
5 | "useDefineForClassFields": true,
6 | "allowSyntheticDefaultImports": true,
7 | "module": "esnext",
8 | "moduleResolution": "node",
9 | "strict": true,
10 | "allowJs": true,
11 | "jsx": "preserve",
12 | "sourceMap": true,
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "esModuleInterop": true,
16 | "lib": [
17 | "esnext",
18 | "dom"
19 | ],
20 | "skipLibCheck": true,
21 | "baseUrl": ".",
22 | "paths": {
23 | "@/*": [
24 | "./src/*"
25 | ]
26 | }
27 | },
28 | "files": [],
29 | "include": [
30 | "packages/**/*.ts",
31 | "src/**/*.ts",
32 | "src/**/*.d.ts",
33 | "src/**/*.tsx",
34 | "src/**/*.vue",
35 | "typings/**/*.ts",
36 | "typings/**/*.d.ts",
37 | "src/hooks/useMaterial.js",
38 | ],
39 | "references": [
40 | {
41 | "path": "./tsconfig.node.json"
42 | }
43 | ]
44 | }
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "esnext",
5 | "allowSyntheticDefaultImports": true,
6 | "moduleResolution": "node"
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/typings/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module '*.vue' {
4 | import type { DefineComponent } from 'vue';
5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
6 | const component: DefineComponent<{}, {}, any>;
7 | export default component;
8 | }
9 |
--------------------------------------------------------------------------------
/typings/modules.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @Author: June
3 | * @Description:
4 | * @Date: 2024-01-06 21:47:34
5 | * @LastEditors: June 1601745371@qq.com
6 | * @LastEditTime: 2024-03-21 14:48:22
7 | */
8 | /*
9 | * @Description: Declarations for node modules.
10 | */
11 |
12 | declare module 'view-ui-plus/dist/locale/zh-CN';
13 | declare module 'view-ui-plus/dist/locale/en-US';
14 |
--------------------------------------------------------------------------------