├── .editorconfig
├── .eslintignore
├── .github
├── FUNDING.yml
└── workflows
│ └── test.yml
├── .gitignore
├── .npmrc
├── .vscode
├── extensions.json
└── settings.json
├── LICENSE
├── README.md
├── README.zh-CN.md
├── auto-imports.d.ts
├── components.d.ts
├── cypress.config.ts
├── cypress
├── e2e
│ ├── basic.spec.ts
│ └── superellipse.spec.ts
└── tsconfig.json
├── eslint.config.js
├── index.html
├── netlify.toml
├── package.json
├── public
├── favicon.svg
└── noise.png
├── shims.d.ts
├── src
├── App.vue
├── assets
│ └── demo
│ │ ├── demo01.svg
│ │ ├── demo02.svg
│ │ ├── demo03.svg
│ │ ├── demo04.svg
│ │ ├── demo05.svg
│ │ ├── demo06.svg
│ │ ├── demo07.svg
│ │ ├── demo08.svg
│ │ ├── demo09.svg
│ │ ├── demo10.svg
│ │ ├── demo11.svg
│ │ └── demo12.svg
├── components
│ ├── GeneratorBySVG.vue
│ ├── layout
│ │ ├── Code.vue
│ │ ├── Demo.vue
│ │ ├── Footer.vue
│ │ ├── Header.vue
│ │ ├── Options.vue
│ │ ├── Preview.vue
│ │ └── Tools.vue
│ └── ui
│ │ ├── Alert.vue
│ │ ├── Background.vue
│ │ ├── ColorPicker.vue
│ │ ├── ConfettiCanvas.vue
│ │ ├── HighlightCode.vue
│ │ ├── Modal.vue
│ │ ├── RangeBar.vue
│ │ ├── TextareaCode.vue
│ │ └── money-card
│ │ ├── index.vue
│ │ ├── wx.jpg
│ │ └── zfb.jpg
├── composables
│ ├── dark.ts
│ └── index.ts
├── i18n
│ ├── cn.ts
│ ├── en.ts
│ └── index.ts
├── main.ts
├── pages
│ ├── README.md
│ ├── [...all].vue
│ └── index.vue
├── styles
│ └── main.css
└── utils
│ ├── RandomBg.ts
│ ├── confetti.ts
│ ├── encodeSvg.ts
│ └── svg.ts
├── test
├── __snapshots__
│ └── component.test.ts.snap
├── basic.test.ts
└── component.test.ts
├── tsconfig.json
├── uno.config.ts
└── vite.config.ts
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # .eslintignore
2 | **/*.png
3 | **/*.jpg
4 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: antfu
2 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | pull_request:
9 | branches:
10 | - main
11 |
12 | jobs:
13 | build:
14 | runs-on: ${{ matrix.os }}
15 |
16 | timeout-minutes: 10
17 |
18 | strategy:
19 | matrix:
20 | node_version: [lts/*]
21 | os: [ubuntu-latest, windows-latest]
22 | fail-fast: false
23 |
24 | steps:
25 | - uses: actions/checkout@v3
26 | - uses: pnpm/action-setup@v2
27 |
28 | - name: Set node version to ${{ matrix.node_version }}
29 | uses: actions/setup-node@v3
30 | with:
31 | node-version: ${{ matrix.node_version }}
32 | cache: pnpm
33 |
34 | - name: Install
35 | run: pnpm i
36 |
37 | - name: Build
38 | run: pnpm run build
39 |
40 | - name: Test
41 | run: pnpm run test
42 |
43 | - name: Lint
44 | run: pnpm run lint
45 |
46 | - name: TypeCheck
47 | run: pnpm run typecheck
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .vite-ssg-dist
3 | .vite-ssg-temp
4 | *.local
5 | dist
6 | dist-ssr
7 | node_modules
8 | .idea/
9 | *.log
10 | pnpm-lock.yaml
11 | cypress/downloads
12 | test/__snapshots__
13 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | shamefully-hoist=true
2 | strict-peer-dependencies=false
3 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "antfu.vite",
4 | "antfu.iconify",
5 | "antfu.unocss",
6 | "antfu.goto-alias",
7 | "vue.volar",
8 | "dbaeumer.vscode-eslint",
9 | "EditorConfig.EditorConfig"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": ["Vitesse", "Vite", "unocss", "vitest", "vueuse", "pinia", "demi", "antfu", "iconify", "intlify", "vitejs", "unplugin", "pnpm"],
3 | "i18n-ally.sourceLanguage": "en",
4 | "i18n-ally.keystyle": "nested",
5 | "i18n-ally.localesPaths": "locales",
6 | "i18n-ally.sortKeys": true,
7 | "prettier.enable": false,
8 |
9 | "files.associations": {
10 | "*.css": "postcss"
11 | },
12 | "editor.formatOnSave": false,
13 |
14 | // Enable the ESlint flat config support
15 | "eslint.experimental.useFlatConfig": true,
16 |
17 | // Auto fix
18 | "editor.codeActionsOnSave": {
19 | "source.fixAll.eslint": "explicit",
20 | "source.organizeImports": "never"
21 | },
22 |
23 | // Silent the stylistic rules in you IDE, but still auto fix them
24 | "eslint.rules.customizations": [
25 | { "rule": "style/*", "severity": "off" },
26 | { "rule": "format/*", "severity": "off" },
27 | { "rule": "*-indent", "severity": "off" },
28 | { "rule": "*-spacing", "severity": "off" },
29 | { "rule": "*-spaces", "severity": "off" },
30 | { "rule": "*-order", "severity": "off" },
31 | { "rule": "*-dangle", "severity": "off" },
32 | { "rule": "*-newline", "severity": "off" },
33 | { "rule": "*quotes", "severity": "off" },
34 | { "rule": "*semi", "severity": "off" }
35 | ],
36 |
37 | // The following is optional.
38 | // It's better to put under project setting `.vscode/settings.json`
39 | // to avoid conflicts with working with different eslint configs
40 | // that does not support all formats.
41 | "eslint.validate": [
42 | "javascript",
43 | "javascriptreact",
44 | "typescript",
45 | "typescriptreact",
46 | "vue",
47 | "html",
48 | "markdown",
49 | "json",
50 | "jsonc",
51 | "yaml"
52 | ]
53 | }
54 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020-PRESENT Anthony Fu
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 | ## Features
11 |
12 | - [√] 设置曲率 N 值
13 | - [√] 设置 stroke fill color
14 | - [√] 设置 stroke width
15 | - [√] 设置 rotate
16 | - [√] SVG code
17 | - [√] 导出 SVG
18 | - [√] 彩带效果
19 | - [√] CSS Background Code
20 |
21 | ## Points
22 |
23 | 1. 核心代码调整超椭圆的 N 值
24 |
25 | 2. 设置都是针对 SVG 的属性设置
26 |
27 | 3. 直接复制用到 background-image 的 CSS 代码,就是将 SVG 代码转换成 base64 编码
28 |
29 | 4. 小彩蛋,按空格键更换背景( 自己之前写的 [random-bg](https://github.com/pinky-pig/what-is-my-random-bg) )
30 |
31 | 5. 彩带效果 [canvas-confetti](https://github.com/catdad/canvas-confetti)
32 |
--------------------------------------------------------------------------------
/README.zh-CN.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 | ## Features
11 |
12 | - [√] Curvature N
13 | - [√] Stroke fill color
14 | - [√] Stroke width
15 | - [√] Rotate
16 | - [√] SVG code
17 | - [√] Export SVG
18 | - [√] Confetti
19 | - [×] CSS Background Code
20 |
--------------------------------------------------------------------------------
/components.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* prettier-ignore */
3 | // @ts-nocheck
4 | // Generated by unplugin-vue-components
5 | // Read more: https://github.com/vuejs/core/pull/3399
6 | export {}
7 |
8 | declare module 'vue' {
9 | export interface GlobalComponents {
10 | Alert: typeof import('./src/components/ui/Alert.vue')['default']
11 | Background: typeof import('./src/components/ui/Background.vue')['default']
12 | Code: typeof import('./src/components/layout/Code.vue')['default']
13 | ColorPicker: typeof import('./src/components/ui/ColorPicker.vue')['default']
14 | ConfettiCanvas: typeof import('./src/components/ui/ConfettiCanvas.vue')['default']
15 | Demo: typeof import('./src/components/layout/Demo.vue')['default']
16 | Footer: typeof import('./src/components/layout/Footer.vue')['default']
17 | GeneratorBySVG: typeof import('./src/components/GeneratorBySVG.vue')['default']
18 | Header: typeof import('./src/components/layout/Header.vue')['default']
19 | HighlightCode: typeof import('./src/components/ui/HighlightCode.vue')['default']
20 | Modal: typeof import('./src/components/ui/Modal.vue')['default']
21 | MoneyCard: typeof import('./src/components/ui/money-card/index.vue')['default']
22 | Options: typeof import('./src/components/layout/Options.vue')['default']
23 | Preview: typeof import('./src/components/layout/Preview.vue')['default']
24 | RangeBar: typeof import('./src/components/ui/RangeBar.vue')['default']
25 | RouterLink: typeof import('vue-router')['RouterLink']
26 | RouterView: typeof import('vue-router')['RouterView']
27 | TextareaCode: typeof import('./src/components/ui/TextareaCode.vue')['default']
28 | Tools: typeof import('./src/components/layout/Tools.vue')['default']
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/cypress.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'cypress'
2 | import vitePreprocessor from 'cypress-vite'
3 |
4 | export default defineConfig({
5 | e2e: {
6 | baseUrl: 'http://localhost:1213',
7 | chromeWebSecurity: false,
8 | specPattern: 'cypress/e2e/**/*.spec.*',
9 | supportFile: false,
10 | setupNodeEvents(on) {
11 | on('file:preprocessor', vitePreprocessor())
12 | },
13 | },
14 |
15 | component: {
16 | devServer: {
17 | framework: 'vue',
18 | bundler: 'vite',
19 | },
20 | },
21 | })
22 |
--------------------------------------------------------------------------------
/cypress/e2e/basic.spec.ts:
--------------------------------------------------------------------------------
1 | // 1. 这是 Cypress 中的测试上下文(context),用于组织测试用例。在这里,我们创建了一个名为 "Basic" 的测试上下文。
2 | context('Basic', () => {
3 | // 2.钩子函数,在每个测试用例运行之前都会执行。
4 | // 它的作用是在测试开始之前使用 cy.visit('/') 命令访问网站的根URL,也就是 http://localhost:1213/。
5 | beforeEach(() => {
6 | cy.visit('/')
7 | })
8 |
9 | // 3.这是一个具体的测试用例,用于测试基本的导航和页面内容是否存在。测试用例的名称是 "basic nav"。
10 | it('basic nav', () => {
11 | // 4.这一行代码使用 cy.url() 命令获取当前页面的URL,
12 | // 然后使用 .should('eq', 'http://localhost:1213/') 断言来验证当前页面的URL是否等于指定的URL,即 http://localhost:1213/。
13 | // 这是一个基本的导航测试,确保页面正确加载。
14 | cy.url()
15 | .should('eq', 'http://localhost:1213/')
16 |
17 | // 5. 这一行代码使用 cy.contains('Superellipse SVG') 命令查找页面上是否存在包含文本 "Superellipse SVG" 的元素,
18 | // 并使用 .should('exist') 断言来验证该元素是否存在于页面上。
19 | // 如果存在,测试就会通过。
20 | cy.contains('Superellipse SVG')
21 | .should('exist')
22 | })
23 |
24 | // 6.总的来说,这个测试脚本的目的是打开一个网页,然后检查当前页面的URL是否正确,
25 | // 并验证页面上是否存在包含文本 "[Superellipse SVG]" 的元素。如果这两个断言都成功,
26 | // 测试就会通过,否则测试会失败,提示问题所在。
27 | // 这是一个简单的示例,展示了 Cypress 如何用于测试网页导航和页面内容。
28 | })
29 |
--------------------------------------------------------------------------------
/cypress/e2e/superellipse.spec.ts:
--------------------------------------------------------------------------------
1 | describe('Superellipse', () => {
2 | beforeEach(() => {
3 | cy.visit('/')
4 | })
5 |
6 | it('测试触发改变随机背景', () => {
7 | cy.get('random-bg')
8 | .shadow()
9 | .find('#whatIsMyRandomBG')
10 | .find('div')
11 | .first()
12 | .should('have.css', 'background')
13 | .then((initialBackgroundColor) => {
14 | cy.get('#superellipse')
15 | .click()
16 |
17 | // 再次获取背景颜色并断言是否发生了变化
18 | cy.get('random-bg')
19 | .shadow()
20 | .find('#whatIsMyRandomBG')
21 | .find('div')
22 | .first()
23 | .should('have.css', 'background')
24 | .and('not.eq', initialBackgroundColor)
25 | })
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/cypress/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "types": [
5 | "cypress"
6 | ]
7 | },
8 | "include": [
9 | "**/*.ts"
10 | ],
11 | "exclude": []
12 | }
13 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import antfu from '@antfu/eslint-config'
2 |
3 | export default antfu(
4 | {
5 | formatters: true,
6 | // TypeScript and Vue are auto-detected, you can also explicitly enable them:
7 | typescript: true,
8 | vue: true,
9 | // Disable jsonc and yaml support
10 | jsonc: false,
11 | yaml: false,
12 | md: false,
13 | // `.eslintignore` is no longer supported in Flat config, use `ignores` instead
14 | ignores: [
15 | '**/fixtures',
16 | ],
17 | vue: {
18 | overrides: {
19 | 'vue/operator-linebreak': ['error', 'before'],
20 | },
21 | },
22 | typescript: {
23 | overrides: {
24 | },
25 | },
26 | yaml: {
27 | overrides: {
28 | },
29 | },
30 |
31 | },
32 | // From the second arguments they are ESLint Flat Configs
33 | // you can have multiple configs
34 | {
35 | files: ['**/*.ts'],
36 | rules: {
37 | 'ts/consistent-type-definitions': 'off',
38 | },
39 | },
40 | {
41 | // Without `files`, they are general rules for all files
42 | rules: {
43 | 'style/semi': ['error', 'never'],
44 | 'unused-imports/no-unused-vars': 'off',
45 | },
46 | },
47 |
48 | )
49 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Superellipse SVG
8 |
9 |
10 |
11 |
12 |
15 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build.environment]
2 | NPM_FLAGS = "--version"
3 | NODE_VERSION = "16"
4 |
5 | [build]
6 | publish = "dist"
7 | command = "npx pnpm i && npx pnpm run build"
8 |
9 | [[redirects]]
10 | from = "/*"
11 | to = "/index.html"
12 | status = 200
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "module",
3 | "private": true,
4 | "packageManager": "pnpm@8.7.6",
5 | "scripts": {
6 | "build": "vite build",
7 | "dev": "vite --port 1213 --open --host",
8 | "lint": "eslint .",
9 | "typecheck": "vue-tsc --noEmit",
10 | "preview": "vite preview",
11 | "test": "vitest",
12 | "test:e2e": "cypress open",
13 | "up": "taze major -I",
14 | "postinstall": "npx simple-git-hooks"
15 | },
16 | "dependencies": {
17 | "@vueuse/core": "^10.4.1",
18 | "canvas-confetti": "^1.6.0",
19 | "prismjs": "^1.29.0",
20 | "vue": "^3.3.4",
21 | "vue-i18n": "^9.4.1",
22 | "vue-router": "^4.2.5"
23 | },
24 | "devDependencies": {
25 | "@antfu/eslint-config": "^2.3.1",
26 | "@iconify-json/carbon": "^1.1.21",
27 | "@iconify-json/fluent-emoji": "^1.1.13",
28 | "@types/canvas-confetti": "^1.6.0",
29 | "@types/node": "^20.6.3",
30 | "@types/prismjs": "^1.26.0",
31 | "@typescript-eslint/eslint-plugin": "^5.59.2",
32 | "@unocss/eslint-config": "^0.56.1",
33 | "@unocss/eslint-plugin": "^0.56.1",
34 | "@unocss/reset": "^0.56.1",
35 | "@vitejs/plugin-vue": "^4.3.4",
36 | "@vitest/ui": "^0.34.4",
37 | "@vue-macros/volar": "^0.14.3",
38 | "@vue/test-utils": "^2.4.1",
39 | "cypress": "^13.2.0",
40 | "cypress-vite": "^1.4.2",
41 | "eslint": "^8.55.0",
42 | "eslint-plugin-cypress": "^2.15.1",
43 | "eslint-plugin-format": "^0.0.1",
44 | "jsdom": "^22.1.0",
45 | "lint-staged": "^14.0.1",
46 | "pnpm": "^8.7.6",
47 | "simple-git-hooks": "^2.9.0",
48 | "taze": "^0.11.2",
49 | "typescript": "^5.2.2",
50 | "unocss": "^0.56.1",
51 | "unplugin-auto-import": "^0.16.6",
52 | "unplugin-vue-components": "^0.25.2",
53 | "unplugin-vue-macros": "^2.5.1",
54 | "vite": "^4.4.9",
55 | "vite-plugin-pages": "^0.31.0",
56 | "vite-plugin-prismjs": "^0.0.8",
57 | "vitest": "^0.34.5",
58 | "vue-tsc": "^1.8.13"
59 | },
60 | "simple-git-hooks": {
61 | "pre-commit": "pnpm lint-staged"
62 | },
63 | "lint-staged": {
64 | "*": "eslint --fix"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
--------------------------------------------------------------------------------
/public/noise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinky-pig/superellipse-svg/cb2b2cb2e071a9c3b916110451cd780bd1f6a2a8/public/noise.png
--------------------------------------------------------------------------------
/shims.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import type { DefineComponent } from 'vue'
3 | const component: DefineComponent<{}, {}, any>
4 | export default component
5 | }
6 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
26 |
--------------------------------------------------------------------------------
/src/assets/demo/demo01.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/demo/demo02.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/demo/demo03.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/demo/demo04.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/demo/demo06.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/demo/demo07.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/demo/demo08.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/demo/demo10.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/demo/demo11.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/demo/demo12.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/GeneratorBySVG.vue:
--------------------------------------------------------------------------------
1 |
38 |
39 |
40 |
51 |
52 |
--------------------------------------------------------------------------------
/src/components/layout/Code.vue:
--------------------------------------------------------------------------------
1 |
30 |
31 |
32 |
33 |
34 |
38 |
39 |
49 |
50 |
54 |
55 |
59 |
60 |
61 |
62 |
63 |
66 |
--------------------------------------------------------------------------------
/src/components/layout/Demo.vue:
--------------------------------------------------------------------------------
1 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | {{ t('other.demoTitle') }}
65 |
66 | {{ t('other.demoText') }}
67 |
68 |
69 |
70 |
71 |
72 |
82 |
83 |
84 |
85 |
86 |
87 |
90 |
--------------------------------------------------------------------------------
/src/components/layout/Footer.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
20 |
21 | |
22 |
23 |
29 | GitHub
30 |
31 |
32 |
33 |
34 |
37 |
--------------------------------------------------------------------------------
/src/components/layout/Header.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
18 |
19 |
20 |

25 |
26 | Superellipse SVG
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
44 |
45 |
50 |
51 |
52 |
53 |
54 |
59 |
--------------------------------------------------------------------------------
/src/components/layout/Options.vue:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 |
37 |
38 |
44 |
45 |
46 | {{ t('option.curvature') }}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
59 |
60 |
61 | {{ t('option.curvatureX') }}
62 |
63 |
64 |
65 |
66 |
67 |
73 |
74 |
75 | {{ t('option.curvatureY') }}
76 |
77 |
78 |
79 |
80 |
81 |
87 |
88 |
89 |
90 | {{ t('option.stroke') }}
91 |
92 |
96 |
97 |
98 |
99 | {{ t('option.fill') }}
100 |
101 |
105 |
106 |
107 |
113 |
114 |
115 | {{ t('option.strokeWidth') }}
116 |
117 |
118 |
119 |
120 |
121 |
127 |
128 |
129 | {{ t('option.rotate') }}
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
147 |
--------------------------------------------------------------------------------
/src/components/layout/Preview.vue:
--------------------------------------------------------------------------------
1 |
62 |
63 |
64 |
81 |
82 |
--------------------------------------------------------------------------------
/src/components/layout/Tools.vue:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 |
37 |
38 |
44 |
45 |
46 |
47 |
48 |
66 |
--------------------------------------------------------------------------------
/src/components/ui/Alert.vue:
--------------------------------------------------------------------------------
1 |
31 |
32 |
33 |
34 |
35 |
36 |
{{ info }}
37 |
38 |
39 |
40 |
41 |
80 |
--------------------------------------------------------------------------------
/src/components/ui/Background.vue:
--------------------------------------------------------------------------------
1 |
40 |
41 |
42 |
48 |
49 |
--------------------------------------------------------------------------------
/src/components/ui/ColorPicker.vue:
--------------------------------------------------------------------------------
1 |
32 |
33 |
34 |
53 |
54 |
55 |
64 |
--------------------------------------------------------------------------------
/src/components/ui/ConfettiCanvas.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/src/components/ui/HighlightCode.vue:
--------------------------------------------------------------------------------
1 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/components/ui/Modal.vue:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
140 |
--------------------------------------------------------------------------------
/src/components/ui/RangeBar.vue:
--------------------------------------------------------------------------------
1 |
26 |
27 |
28 |
29 |
30 |
31 | {{ value }}
32 |
33 |
41 |
42 |
43 |
44 |
75 |
--------------------------------------------------------------------------------
/src/components/ui/TextareaCode.vue:
--------------------------------------------------------------------------------
1 |
30 |
31 |
32 |
33 |
34 |
35 | {{ title }}
36 |
37 |
38 |
51 |
52 |
65 |
66 |
67 | 已拷贝
68 |
69 |
70 |
71 |
76 |
77 |
78 |
79 |
80 |
95 |
--------------------------------------------------------------------------------
/src/components/ui/money-card/wx.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinky-pig/superellipse-svg/cb2b2cb2e071a9c3b916110451cd780bd1f6a2a8/src/components/ui/money-card/wx.jpg
--------------------------------------------------------------------------------
/src/components/ui/money-card/zfb.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinky-pig/superellipse-svg/cb2b2cb2e071a9c3b916110451cd780bd1f6a2a8/src/components/ui/money-card/zfb.jpg
--------------------------------------------------------------------------------
/src/composables/dark.ts:
--------------------------------------------------------------------------------
1 | export const isDark = useDark()
2 | export const toggleDark = useToggle(isDark)
3 |
--------------------------------------------------------------------------------
/src/composables/index.ts:
--------------------------------------------------------------------------------
1 | export * from './dark'
2 |
--------------------------------------------------------------------------------
/src/i18n/cn.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | tool: {
3 | export: '导出 SVG',
4 | copy: '拷贝 SVG',
5 | preview: '预览 CSS',
6 | demo: '预设图形',
7 | },
8 | option: {
9 | curvature: '曲率',
10 | curvatureX: '曲率 X',
11 | curvatureY: '曲率 Y',
12 | stroke: '描边',
13 | fill: '填充',
14 | strokeWidth: '描边宽度',
15 | rotate: '旋转度数',
16 | },
17 | other: {
18 | alertTextSuccess: '已拷贝到剪切板',
19 | alertTextFailed: '拷贝失败',
20 | demoTitle: '预设图形',
21 | demoText: '(点击图形自动粘贴到剪切板)',
22 | },
23 | }
24 |
--------------------------------------------------------------------------------
/src/i18n/en.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | tool: {
3 | export: 'Export SVG',
4 | copy: 'Copy SVG',
5 | preview: 'Preview CSS',
6 | demo: 'Preset Shapes',
7 | },
8 | option: {
9 | curvature: 'Curvature',
10 | curvatureX: 'Curvature X',
11 | curvatureY: 'Curvature Y',
12 | stroke: 'Stroke',
13 | fill: 'Fill',
14 | strokeWidth: 'StrokeWidth',
15 | rotate: 'Rotate',
16 | },
17 | other: {
18 | alertTextSuccess: 'Successfully copied to clipboard',
19 | alertTextFailed: 'Successfully copied to clipboard',
20 | demoTitle: 'Shapes',
21 | demoText: '(Click on the graphic to automatically copy it to the clipboard.)',
22 | },
23 | }
24 |
--------------------------------------------------------------------------------
/src/i18n/index.ts:
--------------------------------------------------------------------------------
1 | import { createI18n } from 'vue-i18n'
2 | import en from './en'
3 | import cn from './cn'
4 |
5 | export enum Locale {
6 | CN = 'zh',
7 | EN = 'en',
8 | }
9 |
10 | /**
11 | * 有两种语言,中文和英文。
12 | * 判断当前语言模式,优先本地语言。
13 | */
14 | const messages = { en, cn }
15 |
16 | const [locale, fallbackLocale] = /^cn\b/.test(window.navigator.language)
17 | ? [Locale.CN, Locale.EN]
18 | : [Locale.EN, Locale.CN]
19 |
20 | export const i18n = createI18n({
21 | locale,
22 | fallbackLocale,
23 | messages,
24 | })
25 |
26 | export const t = i18n.global.t
27 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import { createRouter, createWebHistory } from 'vue-router'
3 | import routes from 'virtual:generated-pages'
4 | import App from './App.vue'
5 | import { i18n } from './i18n/index'
6 |
7 | import '@unocss/reset/tailwind.css'
8 | import './styles/main.css'
9 | import 'uno.css'
10 |
11 | const app = createApp(App)
12 | const router = createRouter({
13 | history: createWebHistory(import.meta.env.BASE_URL),
14 | routes,
15 | })
16 | app.use(router)
17 | app.use(i18n)
18 |
19 | app.mount('#app')
20 |
--------------------------------------------------------------------------------
/src/pages/README.md:
--------------------------------------------------------------------------------
1 | ## File-based Routing
2 |
3 | Routes will be auto-generated for Vue files in this dir with the same file structure.
4 | Check out [`vite-plugin-pages`](https://github.com/hannoeru/vite-plugin-pages) for more details.
5 |
6 | ### Path Aliasing
7 |
8 | `~/` is aliased to `./src/` folder.
9 |
10 | For example, instead of having
11 |
12 | ```ts
13 | import { isDark } from '../../../../composables'
14 | ```
15 |
16 | now, you can use
17 |
18 | ```ts
19 | import { isDark } from '~/composables'
20 | ```
21 |
--------------------------------------------------------------------------------
/src/pages/[...all].vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Not Found
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/pages/index.vue:
--------------------------------------------------------------------------------
1 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
125 |
--------------------------------------------------------------------------------
/src/styles/main.css:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | #app {
4 | height: 100%;
5 | margin: 0;
6 | padding: 0;
7 | }
8 |
9 | html.dark {
10 | background: #121212;
11 | }
12 |
13 |
14 | /* 滚动条样式 */
15 | ::-webkit-scrollbar{
16 | width:6px;
17 | height:6px;
18 | background-color: transparent;
19 | border-radius: 8px;
20 | }
21 | ::-webkit-scrollbar-track-piece{
22 | width:24px;
23 | background-color:transparent;
24 | border-radius: 8px;
25 | }
26 | ::-webkit-scrollbar-thumb:vertical{
27 | width:24px;
28 | height:24px;
29 | background: rgba(214, 212, 212,.3);
30 | border-radius: 8px;
31 | border: 1px solid var(--scrollbar-color);
32 | }
33 | ::-webkit-scrollbar-thumb:horizontal{
34 | width:24px;
35 | height:24px;
36 | background: rgba(214, 212, 212,.3);
37 | border-radius: 8px;
38 | border: 1px solid var(--scrollbar-color);
39 | }
40 | ::-webkit-scrollbar-button{
41 | display:none;
42 | }
43 | ::-webkit-scrollbar-corner{
44 | display:none;
45 | }
46 |
--------------------------------------------------------------------------------
/src/utils/RandomBg.ts:
--------------------------------------------------------------------------------
1 | // -----------------------------------------------------------------------------------------//
2 | // --- https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_custom_elements --//
3 | // --- https://github.com/mdn/web-components-examples/tree/main/life-cycle-callbacks --//
4 | // --- https://mdn.github.io/web-components-examples/life-cycle-callbacks/ --//
5 | // -----------------------------------------------------------------------------------------//
6 |
7 | // 定义DOM
8 | const template = document.createElement('template')
9 | template.innerHTML = `
10 |
23 |
24 |
25 | `
26 |
27 | // 色域值
28 | const themes = [
29 | '64a6bd-90a8c3-ada7c9-d7b9d5-f4cae0'.split('-').map(a => `#${a}`),
30 | '4059ad-6b9ac4-97d8c4-eff2f1-f4b942'.split('-').map(a => `#${a}`),
31 | 'd1f0b1-b6cb9e-92b4a7-8c8a93-81667a'.split('-').map(a => `#${a}`),
32 | '7776bc-cdc7e5-fffbdb-ffec51-ff674d'.split('-').map(a => `#${a}`),
33 | '628395-96897b-dbad6a-cf995f-d0ce7c'.split('-').map(a => `#${a}`),
34 | '28536b-c2948a-7ea8be-f6f0ed-bbb193'.split('-').map(a => `#${a}`),
35 | 'dcc48e-eaefd3-b3c0a4-505168-27233a'.split('-').map(a => `#${a}`),
36 | 'dab6c4-7b886f-b4dc7f-feffa5-ffa0ac'.split('-').map(a => `#${a}`),
37 | ]
38 | // 随机方法
39 | function getRandomIntInclusive(min: number, max: number) {
40 | min = Math.ceil(min)
41 | max = Math.floor(max)
42 | return Math.floor(Math.random() * (max - min + 1)) + min // 含最大值,含最小值
43 | }
44 | // function random(min:number, max:number) {
45 | // return Math.floor(Math.random() * (max - min + 1) + min);
46 | // }
47 | // let color = `rgb(${random(0, 255)}, ${random(0, 255)}, ${random(0, 255)})`
48 |
49 | // 生成多边形
50 | function randomGeneratePolygon(themes: any) {
51 | // 渲染几个多边形(这里只有3个)
52 | const polygonList = Array(getRandomIntInclusive(3, 3)).fill([])
53 |
54 | // 随机这几个多边形的颜色数组
55 | const polygonColorArray = getRandomIntInclusive(0, themes.length - 1)
56 |
57 | // 遍历每个多边形
58 | return polygonList.map((_item, index) => {
59 | // 1.首先获取每个多边形随机的边数
60 | const num = Array(getRandomIntInclusive(3, 10)).fill([])
61 |
62 | // 2.然后计算每个角的坐标
63 | const coordinates = num.map(() => {
64 | // 获取x坐标(这里三个图形各三分之一,所以使用三等分)
65 | const x = getRandomIntInclusive(100 / 3 * index, 100 / 3 * (index + 1))
66 | // 获取y坐标
67 | const y = getRandomIntInclusive(0, 100)
68 |
69 | return [`${x}%`, `${y}%`]
70 | })
71 |
72 | // 3.根据得到的坐标,生成clip-path字符串,n条边即是n个角,n个坐标,坐标范围要在画布最大最小的范围内
73 | let clipPathStr = ''
74 | coordinates.forEach((i) => {
75 | const str = `${i[0]} ${i[1]},`
76 | clipPathStr += str
77 | })
78 |
79 | return {
80 | path: `polygon(${clipPathStr.slice(0, clipPathStr.length - 1)})`,
81 | color: themes[polygonColorArray][index],
82 | }
83 | })
84 | }
85 |
86 | export function generateRandomBg() {
87 | class RandomBg extends HTMLElement {
88 | shadow: ShadowRoot
89 | constructor() {
90 | super()
91 | this.shadow = this.attachShadow({
92 | mode: 'open',
93 | })
94 | this.render()
95 | }
96 |
97 | static get observedAttributes() {
98 | return ['rerender', 'initial']
99 | }
100 |
101 | get rerender() {
102 | return this.getAttribute('rerender')
103 | }
104 |
105 | set rerender(value) {
106 | this.setAttribute('rerender', `${value}`)
107 | }
108 |
109 | get initial() {
110 | // return this.hasAttribute('dblable') // boolean
111 | return this.getAttribute('initial')
112 | }
113 |
114 | set initial(value) {
115 | // if (value)
116 | // this.setAttribute('dblable', '')
117 | // else
118 | // this.removeAttribute('dblable')
119 | this.setAttribute('initial', JSON.stringify(value))
120 | }
121 |
122 | get palettes() {
123 | const propsPalettes = this.getAttribute('palettes')
124 | // 如果有
125 | if (propsPalettes) {
126 | try {
127 | return JSON.parse(propsPalettes)
128 | }
129 | catch (error) {
130 | // eslint-disable-next-line no-console
131 | console.log('当前传入的色板值有问题', error)
132 | }
133 | }
134 | else {
135 | return themes
136 | }
137 | }
138 |
139 | set palettes(value) {
140 | this.setAttribute('palettes', JSON.stringify(value))
141 | }
142 |
143 | render(defaultData?: any) {
144 | // 先清除,再添加
145 | while (this.shadow.lastElementChild)
146 | this.shadow.removeChild(this.shadow.lastElementChild)
147 |
148 | // 克隆一份 template 防止重复使用 污染
149 | const content = template.content.cloneNode(true) as HTMLElement
150 | // 获取背景盒子
151 | const container = content.querySelector('#whatIsMyRandomBG') as HTMLElement
152 | // 生成随机多边形 item
153 | const randomBgItems = defaultData || randomGeneratePolygon(this.palettes)
154 | // 遍历将其添加到 shadow-dom
155 | for (let i = 0; i < randomBgItems.length; i++) {
156 | const item = document.createElement('div')
157 | item.setAttribute('style', `width: 100%; height: 100%; position: absolute; clip-path: ${randomBgItems[i].path}; background: ${randomBgItems[i].color}`) // 可行
158 | container.appendChild(item)
159 | }
160 | // 添加
161 | this.shadow.appendChild(content)
162 | this.dispatchEvent(new CustomEvent('rendered', { detail: randomBgItems }))
163 | }
164 |
165 | // ---------- 生命周期函数 ----------//
166 | // 1.当 custom element 首次被插入文档 DOM 时,被调用。
167 | connectedCallback() {
168 | // console.log('custom element 首次被插入文档 DOM')
169 | }
170 |
171 | // 2.当 custom element 从文档 DOM 中删除时,被调用。
172 | disconnectedCallback() {
173 | // console.log('custom element 从文档 DOM 中删除')
174 | }
175 |
176 | // 3.当 custom element 被移动到新的文档时,被调用。
177 | adoptedCallback() {
178 | // console.log('custom element 被移动到新的文档')
179 | }
180 |
181 | // 4.当 custom element 增加、删除、修改自身属性时,被调用。
182 | // 如果需要触发,需要先在 observedAttributes 中定义
183 | attributeChangedCallback(name: string, _oldValue: any, _newValue: any) {
184 | // 重新渲染
185 |
186 | // 第一次进来不重新渲染
187 | if (name === 'rerender' && _oldValue !== null)
188 | this.render()
189 |
190 | // 默认渲染
191 | if (name === 'initial' && _newValue !== '') {
192 | // 1. 默认渲染
193 | let defaultData = null
194 | try {
195 | defaultData = JSON.parse(_newValue)
196 | this.render(defaultData)
197 | }
198 | catch (error) {
199 | console.error('当前传入的初始数据有问题')
200 | }
201 | }
202 | else if (name === 'initial' && _newValue === '') {
203 | // 2.随机渲染
204 | this.render()
205 | }
206 | }
207 | }
208 |
209 | // 防止重复注册
210 | window.customElements.get('random-bg') || window.customElements.define('random-bg', RandomBg)
211 | }
212 |
213 | generateRandomBg()
214 |
--------------------------------------------------------------------------------
/src/utils/confetti.ts:
--------------------------------------------------------------------------------
1 | export function showConfetti() {
2 | import('canvas-confetti').then((confetti) => {
3 | const canvasEle: HTMLCanvasElement | null
4 | = document.querySelector('#confetti')
5 |
6 | if (!canvasEle)
7 | return
8 |
9 | const myConfetti = confetti.create(canvasEle, {
10 | resize: true,
11 | useWorker: true,
12 | disableForReducedMotion: true,
13 | })
14 |
15 | const duration = performance.now() + 1 * 1000
16 |
17 | const confettiColors = ['#6967fe', '#85e9f4', '#e16984'];
18 |
19 | (function frame() {
20 | myConfetti({
21 | particleCount: confettiColors.length,
22 | angle: 60,
23 | spread: 55,
24 | origin: { x: 0 },
25 | colors: confettiColors,
26 | })
27 | myConfetti({
28 | particleCount: confettiColors.length,
29 | angle: 120,
30 | spread: 55,
31 | origin: { x: 1 },
32 | colors: confettiColors,
33 | })
34 |
35 | if (performance.now() < duration)
36 | requestAnimationFrame(frame)
37 | })()
38 | })
39 | }
40 |
--------------------------------------------------------------------------------
/src/utils/encodeSvg.ts:
--------------------------------------------------------------------------------
1 | // https://yoksel.github.io/url-encoder/
2 |
3 | export function getResults(initTextarea: string) {
4 | const namespaced = addNameSpace(initTextarea)
5 | const escaped = encodeSVG(namespaced)
6 | const quotes = getQuotes()
7 |
8 | const resultCss = `background-image: url(${quotes.level1}data:image/svg+xml,${escaped}${quotes.level1});`
9 |
10 | return {
11 | encoded: escaped, // 转化
12 | resultCss, // 直接在 CSS 中使用
13 | }
14 | }
15 |
16 | function addNameSpace(data: string) {
17 | const quotes = getQuotes()
18 |
19 | if (!data.includes('http://www.w3.org/2000/svg'))
20 | data = data.replace(/