├── .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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/core/assets/lock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/core/assets/middlecontrol.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/core/assets/middlecontrolhoz.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/core/assets/rotateicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 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 | 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 | 3 | 6 | -------------------------------------------------------------------------------- /src/assets/icon/align/averageY.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | -------------------------------------------------------------------------------- /src/assets/icon/align/bottom.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | -------------------------------------------------------------------------------- /src/assets/icon/align/centerX.svg: -------------------------------------------------------------------------------- 1 | 9 | 14 | -------------------------------------------------------------------------------- /src/assets/icon/align/centerY.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | -------------------------------------------------------------------------------- /src/assets/icon/align/left.svg: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /src/assets/icon/align/right.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon/align/top.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | -------------------------------------------------------------------------------- /src/assets/icon/attribute/fontStyle.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /src/assets/icon/attribute/fontWeight.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /src/assets/icon/attribute/linethrough.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /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 | 2 | 6 | -------------------------------------------------------------------------------- /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 | 8 | 13 | 18 | -------------------------------------------------------------------------------- /src/assets/icon/centerAlign/centerX.svg: -------------------------------------------------------------------------------- 1 | 8 | 12 | -------------------------------------------------------------------------------- /src/assets/icon/centerAlign/centerY.svg: -------------------------------------------------------------------------------- 1 | 8 | 12 | -------------------------------------------------------------------------------- /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 | 10 | 14 | 18 | 22 | -------------------------------------------------------------------------------- /src/assets/icon/flip/y.svg: -------------------------------------------------------------------------------- 1 | 9 | 13 | 17 | 21 | -------------------------------------------------------------------------------- /src/assets/icon/group/group.svg: -------------------------------------------------------------------------------- 1 | 9 | 13 | -------------------------------------------------------------------------------- /src/assets/icon/group/unGroup.svg: -------------------------------------------------------------------------------- 1 | 9 | 13 | -------------------------------------------------------------------------------- /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 | 11 | 15 | -------------------------------------------------------------------------------- /src/assets/icon/tools/circle.svg: -------------------------------------------------------------------------------- 1 | 10 | 14 | -------------------------------------------------------------------------------- /src/assets/icon/tools/draw1.svg: -------------------------------------------------------------------------------- 1 | 10 | 15 | 20 | -------------------------------------------------------------------------------- /src/assets/icon/tools/draw2.svg: -------------------------------------------------------------------------------- 1 | 10 | 15 | -------------------------------------------------------------------------------- /src/assets/icon/tools/draw3.svg: -------------------------------------------------------------------------------- 1 | 11 | 15 | -------------------------------------------------------------------------------- /src/assets/icon/tools/draw4.svg: -------------------------------------------------------------------------------- 1 | 11 | 15 | -------------------------------------------------------------------------------- /src/assets/icon/tools/polygon.svg: -------------------------------------------------------------------------------- 1 | 10 | 14 | -------------------------------------------------------------------------------- /src/assets/icon/tools/rect.svg: -------------------------------------------------------------------------------- 1 | 10 | 14 | -------------------------------------------------------------------------------- /src/assets/icon/tools/text.svg: -------------------------------------------------------------------------------- 1 | 8 | 12 | -------------------------------------------------------------------------------- /src/assets/icon/tools/triangle.svg: -------------------------------------------------------------------------------- 1 | 10 | 14 | -------------------------------------------------------------------------------- /src/assets/icon/top.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icon/zoom/big.svg: -------------------------------------------------------------------------------- 1 | 10 | 15 | 20 | -------------------------------------------------------------------------------- /src/assets/icon/zoom/small.svg: -------------------------------------------------------------------------------- 1 | 10 | 15 | 20 | -------------------------------------------------------------------------------- /src/components/admin.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 33 | 34 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /src/components/attributeId.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 44 | 45 | 95 | 96 | 113 | -------------------------------------------------------------------------------- /src/components/bgBar.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 97 | 98 | 118 | -------------------------------------------------------------------------------- /src/components/centerAlign.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 38 | 39 | 51 | 63 | -------------------------------------------------------------------------------- /src/components/clipImage.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 86 | 91 | -------------------------------------------------------------------------------- /src/components/clone.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | -------------------------------------------------------------------------------- /src/components/color-picker/comps/TabPanel.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 40 | -------------------------------------------------------------------------------- /src/components/color-picker/comps/svg.vue: -------------------------------------------------------------------------------- 1 | 8 | 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 | 47 | 48 | 93 | 101 | -------------------------------------------------------------------------------- /src/components/common/searchType.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 36 | 37 | 106 | 122 | -------------------------------------------------------------------------------- /src/components/del.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 25 | -------------------------------------------------------------------------------- /src/components/dragMode.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 18 | 19 | 42 | 49 | -------------------------------------------------------------------------------- /src/components/edit.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/components/flip.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 31 | 32 | 45 | 46 | 59 | -------------------------------------------------------------------------------- /src/components/group.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 27 | 28 | 43 | 52 | -------------------------------------------------------------------------------- /src/components/hide.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | 16 | 44 | -------------------------------------------------------------------------------- /src/components/history.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 29 | 30 | 51 | 52 | 63 | 64 | 69 | -------------------------------------------------------------------------------- /src/components/importJSON.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 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 | 24 | 25 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/components/lock.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | 16 | 38 | 39 | 55 | -------------------------------------------------------------------------------- /src/components/logo.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 37 | 65 | -------------------------------------------------------------------------------- /src/components/myMaterial/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | 23 | 43 | 44 | 50 | -------------------------------------------------------------------------------- /src/components/previewCurrent.vue: -------------------------------------------------------------------------------- 1 | 9 | 14 | 15 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/components/replaceImg.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 67 | 72 | -------------------------------------------------------------------------------- /src/components/setSize.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 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 | 38 | 39 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/components/workspaceMask.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 30 | 31 | 38 | -------------------------------------------------------------------------------- /src/components/zoom.vue: -------------------------------------------------------------------------------- 1 | 8 | 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 | 39 | 40 | 69 | 70 | 81 | -------------------------------------------------------------------------------- /src/views/template/components/banner.vue: -------------------------------------------------------------------------------- 1 | 8 | 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 | --------------------------------------------------------------------------------