This is an automated email from the ASF dual-hosted git repository.
wangzx pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/echarts-theme-builder.git
The following commit(s) were added to refs/heads/master by this push:
new fa7209b refine and simplify code & externalize echarts library
fa7209b is described below
commit fa7209b6d4fe08634811e3717a9db5e0e593f02d
Author: plainheart <[email protected]>
AuthorDate: Sat Nov 8 08:28:49 2025 +0800
refine and simplify code & externalize echarts library
---
app.html | 2 +
en/body.html | 2 -
index.html | 17 +-
package-lock.json | 479 +++++++++++++++++++++++++----------
package.json | 21 +-
scripts/release.js | 44 ++--
src/App.vue | 24 +-
src/components/ChartPreviewPanel.vue | 115 +++------
src/components/ColorList.vue | 1 -
src/components/ColorPicker.vue | 1 -
src/components/ThemePanel.vue | 63 +++--
src/composables/useLocalization.ts | 15 +-
src/entries/en.ts | 36 ---
src/entries/zh.ts | 36 ---
src/i18n.ts | 39 ++-
src/locales/en.json | 6 +-
src/locales/zh.json | 6 +-
src/main.ts | 6 +-
src/stores/theme.ts | 29 ++-
src/style.css | 11 +-
src/utils/themeGenerator.ts | 2 +-
vite.config.ts | 191 +++++---------
zh/body.html | 2 -
23 files changed, 591 insertions(+), 557 deletions(-)
diff --git a/app.html b/app.html
new file mode 100644
index 0000000..d713038
--- /dev/null
+++ b/app.html
@@ -0,0 +1,2 @@
+<script type="module" src="/src/main.ts"></script>
+<div id="theme-builder-app"></div>
diff --git a/en/body.html b/en/body.html
deleted file mode 100644
index 1db2bb3..0000000
--- a/en/body.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<div id="theme-builder"></div>
-<script type="module" src="../src/entries/en.ts"></script>
diff --git a/index.html b/index.html
index cae8da6..a633b9c 100644
--- a/index.html
+++ b/index.html
@@ -3,11 +3,24 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <link rel="shortcut icon"
href="https://echarts.apache.org/zh/images/favicon.png?_v_=20240226">
+ <link rel="shortcut icon"
href="https://echarts.apache.org/en/images/favicon.png">
<title>Apache ECharts Theme Builder</title>
+ <style>
+ /* only for dev environment */
+ html, body {
+ height: 100%;
+ margin: 0;
+ }
+ </style>
+ <!-- include echarts CDN lib if NOT IN dev environment -->
+ %VITE_EXTERNAL_ECHARTS_SCRIPT%
+ <script>
+ // show language selector in dev/preview environment
+ window.SHOW_LANGUAGE_SELECTOR = true;
+ </script>
</head>
<body>
- <div id="theme-builder"></div>
+ <div id="theme-builder-app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
diff --git a/package-lock.json b/package-lock.json
index edc6243..c69f70e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,22 +8,23 @@
"name": "echarts-theme-builder",
"version": "0.0.0",
"dependencies": {
- "echarts": "^6.0.0",
"vant": "^4.9.21",
- "vue": "^3.5.18",
+ "vue": "^3.5.24",
"vue-i18n": "^9.14.5",
"vue3-colorpicker": "^2.3.0"
},
"devDependencies": {
"@types/fs-extra": "^11.0.4",
- "@types/node": "^24.4.0",
+ "@types/node": "^24.10.0",
"@vitejs/plugin-vue": "^6.0.1",
- "@vue/tsconfig": "^0.7.0",
+ "@vue/tsconfig": "^0.8.1",
"copy-dir": "^1.3.0",
- "fs-extra": "^11.3.1",
+ "echarts": "^6.0.0",
+ "fs-extra": "^11.3.2",
"typescript": "~5.8.3",
- "vite": "^7.1.2",
- "vue-tsc": "^3.0.5"
+ "vite": "^7.2.2",
+ "vite-plugin-static-copy": "^3.1.4",
+ "vue-tsc": "^3.1.3"
}
},
"node_modules/@aesoper/normal-utils": {
@@ -42,21 +43,21 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.27.1",
- "resolved":
"https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
- "integrity":
"sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "version": "7.28.5",
+ "resolved":
"https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity":
"sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
- "version": "7.28.3",
- "resolved":
"https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz",
- "integrity":
"sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==",
+ "version": "7.28.5",
+ "resolved":
"https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity":
"sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.28.2"
+ "@babel/types": "^7.28.5"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -66,13 +67,13 @@
}
},
"node_modules/@babel/types": {
- "version": "7.28.2",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz",
- "integrity":
"sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+ "integrity":
"sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1"
+ "@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
@@ -896,13 +897,13 @@
}
},
"node_modules/@types/node": {
- "version": "24.4.0",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.4.0.tgz",
- "integrity":
"sha512-gUuVEAK4/u6F9wRLznPUU4WGUacSEBDPoC2TrBkw3GAnOLHBL45QdfHOXp1kJ4ypBGLxTOB+t7NJLpKoC3gznQ==",
+ "version": "24.10.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz",
+ "integrity":
"sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~7.11.0"
+ "undici-types": "~7.16.0"
}
},
"node_modules/@types/web-bluetooth": {
@@ -973,64 +974,53 @@
}
},
"node_modules/@vue/compiler-core": {
- "version": "3.5.20",
- "resolved":
"https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.20.tgz",
- "integrity":
"sha512-8TWXUyiqFd3GmP4JTX9hbiTFRwYHgVL/vr3cqhr4YQ258+9FADwvj7golk2sWNGHR67QgmCZ8gz80nQcMokhwg==",
+ "version": "3.5.24",
+ "resolved":
"https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.24.tgz",
+ "integrity":
"sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.28.3",
- "@vue/shared": "3.5.20",
+ "@babel/parser": "^7.28.5",
+ "@vue/shared": "3.5.24",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
- "version": "3.5.20",
- "resolved":
"https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.20.tgz",
- "integrity":
"sha512-whB44M59XKjqUEYOMPYU0ijUV0G+4fdrHVKDe32abNdX/kJe1NUEMqsi4cwzXa9kyM9w5S8WqFsrfo1ogtBZGQ==",
+ "version": "3.5.24",
+ "resolved":
"https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.24.tgz",
+ "integrity":
"sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-core": "3.5.20",
- "@vue/shared": "3.5.20"
+ "@vue/compiler-core": "3.5.24",
+ "@vue/shared": "3.5.24"
}
},
"node_modules/@vue/compiler-sfc": {
- "version": "3.5.20",
- "resolved":
"https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.20.tgz",
- "integrity":
"sha512-SFcxapQc0/feWiSBfkGsa1v4DOrnMAQSYuvDMpEaxbpH5dKbnEM5KobSNSgU+1MbHCl+9ftm7oQWxvwDB6iBfw==",
+ "version": "3.5.24",
+ "resolved":
"https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.24.tgz",
+ "integrity":
"sha512-8EG5YPRgmTB+YxYBM3VXy8zHD9SWHUJLIGPhDovo3Z8VOgvP+O7UP5vl0J4BBPWYD9vxtBabzW1EuEZ+Cqs14g==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.28.3",
- "@vue/compiler-core": "3.5.20",
- "@vue/compiler-dom": "3.5.20",
- "@vue/compiler-ssr": "3.5.20",
- "@vue/shared": "3.5.20",
+ "@babel/parser": "^7.28.5",
+ "@vue/compiler-core": "3.5.24",
+ "@vue/compiler-dom": "3.5.24",
+ "@vue/compiler-ssr": "3.5.24",
+ "@vue/shared": "3.5.24",
"estree-walker": "^2.0.2",
- "magic-string": "^0.30.17",
+ "magic-string": "^0.30.21",
"postcss": "^8.5.6",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-ssr": {
- "version": "3.5.20",
- "resolved":
"https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.20.tgz",
- "integrity":
"sha512-RSl5XAMc5YFUXpDQi+UQDdVjH9FnEpLDHIALg5J0ITHxkEzJ8uQLlo7CIbjPYqmZtt6w0TsIPbo1izYXwDG7JA==",
- "license": "MIT",
- "dependencies": {
- "@vue/compiler-dom": "3.5.20",
- "@vue/shared": "3.5.20"
- }
- },
- "node_modules/@vue/compiler-vue2": {
- "version": "2.7.16",
- "resolved":
"https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz",
- "integrity":
"sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==",
- "dev": true,
+ "version": "3.5.24",
+ "resolved":
"https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.24.tgz",
+ "integrity":
"sha512-trOvMWNBMQ/odMRHW7Ae1CdfYx+7MuiQu62Jtu36gMLXcaoqKvAyh+P73sYG9ll+6jLB6QPovqoKGGZROzkFFg==",
"license": "MIT",
"dependencies": {
- "de-indent": "^1.0.2",
- "he": "^1.2.0"
+ "@vue/compiler-dom": "3.5.24",
+ "@vue/shared": "3.5.24"
}
},
"node_modules/@vue/devtools-api": {
@@ -1040,17 +1030,16 @@
"license": "MIT"
},
"node_modules/@vue/language-core": {
- "version": "3.0.6",
- "resolved":
"https://registry.npmjs.org/@vue/language-core/-/language-core-3.0.6.tgz",
- "integrity":
"sha512-e2RRzYWm+qGm8apUHW1wA5RQxzNhkqbbKdbKhiDUcmMrNAZGyM8aTiL3UrTqkaFI5s7wJRGGrp4u3jgusuBp2A==",
+ "version": "3.1.3",
+ "resolved":
"https://registry.npmjs.org/@vue/language-core/-/language-core-3.1.3.tgz",
+ "integrity":
"sha512-KpR1F/eGAG9D1RZ0/T6zWJs6dh/pRLfY5WupecyYKJ1fjVmDMgTPw9wXmKv2rBjo4zCJiOSiyB8BDP1OUwpMEA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@volar/language-core": "2.4.23",
"@vue/compiler-dom": "^3.5.0",
- "@vue/compiler-vue2": "^2.7.16",
"@vue/shared": "^3.5.0",
- "alien-signals": "^2.0.5",
+ "alien-signals": "^3.0.0",
"muggle-string": "^0.4.1",
"path-browserify": "^1.0.1",
"picomatch": "^4.0.2"
@@ -1065,59 +1054,59 @@
}
},
"node_modules/@vue/reactivity": {
- "version": "3.5.20",
- "resolved":
"https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.20.tgz",
- "integrity":
"sha512-hS8l8x4cl1fmZpSQX/NXlqWKARqEsNmfkwOIYqtR2F616NGfsLUm0G6FQBK6uDKUCVyi1YOL8Xmt/RkZcd/jYQ==",
+ "version": "3.5.24",
+ "resolved":
"https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.24.tgz",
+ "integrity":
"sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==",
"license": "MIT",
"dependencies": {
- "@vue/shared": "3.5.20"
+ "@vue/shared": "3.5.24"
}
},
"node_modules/@vue/runtime-core": {
- "version": "3.5.20",
- "resolved":
"https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.20.tgz",
- "integrity":
"sha512-vyQRiH5uSZlOa+4I/t4Qw/SsD/gbth0SW2J7oMeVlMFMAmsG1rwDD6ok0VMmjXY3eI0iHNSSOBilEDW98PLRKw==",
+ "version": "3.5.24",
+ "resolved":
"https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.24.tgz",
+ "integrity":
"sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==",
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.20",
- "@vue/shared": "3.5.20"
+ "@vue/reactivity": "3.5.24",
+ "@vue/shared": "3.5.24"
}
},
"node_modules/@vue/runtime-dom": {
- "version": "3.5.20",
- "resolved":
"https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.20.tgz",
- "integrity":
"sha512-KBHzPld/Djw3im0CQ7tGCpgRedryIn4CcAl047EhFTCCPT2xFf4e8j6WeKLgEEoqPSl9TYqShc3Q6tpWpz/Xgw==",
+ "version": "3.5.24",
+ "resolved":
"https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.24.tgz",
+ "integrity":
"sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==",
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.20",
- "@vue/runtime-core": "3.5.20",
- "@vue/shared": "3.5.20",
+ "@vue/reactivity": "3.5.24",
+ "@vue/runtime-core": "3.5.24",
+ "@vue/shared": "3.5.24",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
- "version": "3.5.20",
- "resolved":
"https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.20.tgz",
- "integrity":
"sha512-HthAS0lZJDH21HFJBVNTtx+ULcIbJQRpjSVomVjfyPkFSpCwvsPTA+jIzOaUm3Hrqx36ozBHePztQFg6pj5aKg==",
+ "version": "3.5.24",
+ "resolved":
"https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.24.tgz",
+ "integrity":
"sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-ssr": "3.5.20",
- "@vue/shared": "3.5.20"
+ "@vue/compiler-ssr": "3.5.24",
+ "@vue/shared": "3.5.24"
},
"peerDependencies": {
- "vue": "3.5.20"
+ "vue": "3.5.24"
}
},
"node_modules/@vue/shared": {
- "version": "3.5.20",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.20.tgz",
- "integrity":
"sha512-SoRGP596KU/ig6TfgkCMbXkr4YJ91n/QSdMuqeP5r3hVIYA3CPHUBCc7Skak0EAKV+5lL4KyIh61VA/pK1CIAA==",
+ "version": "3.5.24",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.24.tgz",
+ "integrity":
"sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==",
"license": "MIT"
},
"node_modules/@vue/tsconfig": {
- "version": "0.7.0",
- "resolved":
"https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.7.0.tgz",
- "integrity":
"sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==",
+ "version": "0.8.1",
+ "resolved":
"https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.8.1.tgz",
+ "integrity":
"sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -1222,12 +1211,90 @@
}
},
"node_modules/alien-signals": {
- "version": "2.0.7",
- "resolved":
"https://registry.npmjs.org/alien-signals/-/alien-signals-2.0.7.tgz",
- "integrity":
"sha512-wE7y3jmYeb0+h6mr5BOovuqhFv22O/MV9j5p0ndJsa7z1zJNPGQ4ph5pQk/kTTCWRC3xsA4SmtwmkzQO+7NCNg==",
+ "version": "3.0.6",
+ "resolved":
"https://registry.npmjs.org/alien-signals/-/alien-signals-3.0.6.tgz",
+ "integrity":
"sha512-gCs0YqC1mkYGC6IRXsSrA62ShOSv1FlVN5tRp/Cs2vRWLK/BAeluWIdfsl253pFQPznKEvRmHhfep7crWfyfWQ==",
"dev": true,
"license": "MIT"
},
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity":
"sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/anymatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity":
"sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved":
"https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity":
"sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity":
"sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity":
"sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
"node_modules/copy-dir": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/copy-dir/-/copy-dir-1.3.0.tgz",
@@ -1241,17 +1308,11 @@
"integrity":
"sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
- "node_modules/de-indent": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
- "integrity":
"sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/echarts": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz",
"integrity":
"sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==",
+ "dev": true,
"license": "Apache-2.0",
"dependencies": {
"tslib": "2.3.0",
@@ -1336,10 +1397,23 @@
}
}
},
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved":
"https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity":
"sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/fs-extra": {
- "version": "11.3.1",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz",
- "integrity":
"sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==",
+ "version": "11.3.2",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz",
+ "integrity":
"sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1366,6 +1440,19 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved":
"https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity":
"sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved":
"https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -1381,14 +1468,50 @@
"node": ">=0.10.0"
}
},
- "node_modules/he": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
- "integrity":
"sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved":
"https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity":
"sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"license": "MIT",
- "bin": {
- "he": "bin/he"
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved":
"https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity":
"sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity":
"sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity":
"sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
}
},
"node_modules/is-plain-object": {
@@ -1420,9 +1543,9 @@
"license": "MIT"
},
"node_modules/magic-string": {
- "version": "0.30.18",
- "resolved":
"https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz",
- "integrity":
"sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==",
+ "version": "0.30.21",
+ "resolved":
"https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity":
"sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
@@ -1453,6 +1576,29 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved":
"https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity":
"sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/p-map": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz",
+ "integrity":
"sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/path-browserify": {
"version": "1.0.1",
"resolved":
"https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
@@ -1507,6 +1653,32 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity":
"sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/readdirp/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity":
"sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/rollup": {
"version": "4.48.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.48.1.tgz",
@@ -1563,14 +1735,14 @@
"license": "MIT"
},
"node_modules/tinyglobby": {
- "version": "0.2.14",
- "resolved":
"https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
- "integrity":
"sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
+ "version": "0.2.15",
+ "resolved":
"https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity":
"sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "fdir": "^6.4.4",
- "picomatch": "^4.0.2"
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
},
"engines": {
"node": ">=12.0.0"
@@ -1579,10 +1751,24 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved":
"https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity":
"sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
"node_modules/tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity":
"sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
+ "dev": true,
"license": "0BSD"
},
"node_modules/typescript": {
@@ -1591,6 +1777,7 @@
"integrity":
"sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"devOptional": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -1600,9 +1787,9 @@
}
},
"node_modules/undici-types": {
- "version": "7.11.0",
- "resolved":
"https://registry.npmjs.org/undici-types/-/undici-types-7.11.0.tgz",
- "integrity":
"sha512-kt1ZriHTi7MU+Z/r9DOdAI3ONdaR3M3csEaRc6ewa4f4dTvX4cQCbJ4NkEn0ohE4hHtq85+PhPSTY+pO/1PwgA==",
+ "version": "7.16.0",
+ "resolved":
"https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity":
"sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"dev": true,
"license": "MIT"
},
@@ -1631,18 +1818,19 @@
}
},
"node_modules/vite": {
- "version": "7.1.3",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz",
- "integrity":
"sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==",
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
+ "integrity":
"sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
"picomatch": "^4.0.3",
"postcss": "^8.5.6",
"rollup": "^4.43.0",
- "tinyglobby": "^0.2.14"
+ "tinyglobby": "^0.2.15"
},
"bin": {
"vite": "bin/vite.js"
@@ -1705,6 +1893,25 @@
}
}
},
+ "node_modules/vite-plugin-static-copy": {
+ "version": "3.1.4",
+ "resolved":
"https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-3.1.4.tgz",
+ "integrity":
"sha512-iCmr4GSw4eSnaB+G8zc2f4dxSuDjbkjwpuBLLGvQYR9IW7rnDzftnUjOH5p4RYR+d4GsiBqXRvzuFhs5bnzVyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.6.0",
+ "p-map": "^7.0.3",
+ "picocolors": "^1.1.1",
+ "tinyglobby": "^0.2.15"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
"node_modules/vscode-uri": {
"version": "3.1.0",
"resolved":
"https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
@@ -1713,16 +1920,17 @@
"license": "MIT"
},
"node_modules/vue": {
- "version": "3.5.20",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.20.tgz",
- "integrity":
"sha512-2sBz0x/wis5TkF1XZ2vH25zWq3G1bFEPOfkBcx2ikowmphoQsPH6X0V3mmPCXA2K1N/XGTnifVyDQP4GfDDeQw==",
+ "version": "3.5.24",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.24.tgz",
+ "integrity":
"sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
- "@vue/compiler-dom": "3.5.20",
- "@vue/compiler-sfc": "3.5.20",
- "@vue/runtime-dom": "3.5.20",
- "@vue/server-renderer": "3.5.20",
- "@vue/shared": "3.5.20"
+ "@vue/compiler-dom": "3.5.24",
+ "@vue/compiler-sfc": "3.5.24",
+ "@vue/runtime-dom": "3.5.24",
+ "@vue/server-renderer": "3.5.24",
+ "@vue/shared": "3.5.24"
},
"peerDependencies": {
"typescript": "*"
@@ -1754,14 +1962,14 @@
}
},
"node_modules/vue-tsc": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.0.6.tgz",
- "integrity":
"sha512-Tbs8Whd43R2e2nxez4WXPvvdjGbW24rOSgRhLOHXzWiT4pcP4G7KeWh0YCn18rF4bVwv7tggLLZ6MJnO6jXPBg==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.1.3.tgz",
+ "integrity":
"sha512-StMNfZHwPIXQgY3KxPKM0Jsoc8b46mDV3Fn2UlHCBIwRJApjqrSwqeMYgWf0zpN+g857y74pv7GWuBm+UqQe1w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@volar/typescript": "2.4.23",
- "@vue/language-core": "3.0.6"
+ "@vue/language-core": "3.1.3"
},
"bin": {
"vue-tsc": "bin/vue-tsc.js"
@@ -1814,6 +2022,7 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz",
"integrity":
"sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==",
+ "dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"tslib": "2.3.0"
diff --git a/package.json b/package.json
index ffa6e74..dbec681 100644
--- a/package.json
+++ b/package.json
@@ -6,26 +6,27 @@
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
- "preview": "http-server app",
- "serve": "http-server app",
- "release": "npm run build && node scripts/release.js"
+ "serve:dist": "http-server dist",
+ "serve:app": "http-server app",
+ "release": "npm run build -- --mode app && node scripts/release.js"
},
"dependencies": {
- "echarts": "^6.0.0",
"vant": "^4.9.21",
- "vue": "^3.5.18",
+ "vue": "^3.5.24",
"vue-i18n": "^9.14.5",
"vue3-colorpicker": "^2.3.0"
},
"devDependencies": {
"@types/fs-extra": "^11.0.4",
- "@types/node": "^24.4.0",
+ "@types/node": "^24.10.0",
"@vitejs/plugin-vue": "^6.0.1",
- "@vue/tsconfig": "^0.7.0",
+ "@vue/tsconfig": "^0.8.1",
"copy-dir": "^1.3.0",
- "fs-extra": "^11.3.1",
+ "echarts": "^6.0.0",
+ "fs-extra": "^11.3.2",
"typescript": "~5.8.3",
- "vite": "^7.1.2",
- "vue-tsc": "^3.0.5"
+ "vite": "^7.2.2",
+ "vite-plugin-static-copy": "^3.1.4",
+ "vue-tsc": "^3.1.3"
}
}
diff --git a/scripts/release.js b/scripts/release.js
index 83ad7c0..4e8f01f 100644
--- a/scripts/release.js
+++ b/scripts/release.js
@@ -10,18 +10,19 @@ const __dirname = path.dirname(__filename);
const appDir = path.resolve(__dirname, '../app');
const releaseDestDir = config.releaseDestDir;
const ecWWWGeneratedDir = config.ecWWWGeneratedDir;
+const languages = ['en', 'zh'];
console.log('Releasing theme builder...');
// Make sure directories exist
-fs.ensureDirSync(path.join(ecWWWGeneratedDir, 'en', 'theme-builder'));
-fs.ensureDirSync(path.join(ecWWWGeneratedDir, 'zh', 'theme-builder'));
-fs.ensureDirSync(path.join(releaseDestDir, 'en', 'theme-builder'));
-fs.ensureDirSync(path.join(releaseDestDir, 'zh', 'theme-builder'));
+languages.forEach(lang => {
+ fs.ensureDirSync(path.join(ecWWWGeneratedDir, lang, 'theme-builder'));
+ fs.ensureDirSync(path.join(releaseDestDir, lang, 'theme-builder'));
+});
// Clean up existing JS and CSS files in the releaseDestDir
console.log('Cleaning up old CSS and JS files...');
-['en', 'zh'].forEach(lang => {
+languages.forEach(lang => {
const themeBuilderDir = path.join(releaseDestDir, lang, 'theme-builder');
if (fs.existsSync(themeBuilderDir)) {
const files = fs.readdirSync(themeBuilderDir);
@@ -35,28 +36,23 @@ console.log('Cleaning up old CSS and JS files...');
}
});
-// Move body.html files
-console.log('Moving body.html files to echarts-www...');
-fs.copySync(
- path.join(appDir, 'en', 'body.html'),
- path.join(ecWWWGeneratedDir, 'en', 'theme-builder', 'body.html')
-);
-fs.copySync(
- path.join(appDir, 'zh', 'body.html'),
- path.join(ecWWWGeneratedDir, 'zh', 'theme-builder', 'body.html')
-);
+// Move app.html files to echarts-www body.html
+console.log('Moving app.html files to echarts-www body.html...');
+languages.forEach(lang => {
+ fs.copySync(
+ path.join(appDir, 'app.html'),
+ path.join(ecWWWGeneratedDir, lang, 'theme-builder', 'body.html')
+ );
+});
// Move theme-builder files to website
console.log('Moving theme-builder files to echarts-website...');
-fs.copySync(
- path.join(appDir, 'en', 'theme-builder'),
- path.join(releaseDestDir, 'en', 'theme-builder'),
+languages.forEach(lang => {
+ fs.copySync(
+ path.join(appDir, 'theme-builder'),
+ path.join(releaseDestDir, lang, 'theme-builder'),
{ overwrite: true }
-);
-fs.copySync(
- path.join(appDir, 'zh', 'theme-builder'),
- path.join(releaseDestDir, 'zh', 'theme-builder'),
- { overwrite: true }
-);
+ );
+});
console.log('Release completed successfully!');
diff --git a/src/App.vue b/src/App.vue
index 3867abf..5e28642 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,5 +1,5 @@
<script setup lang="ts">
-import { ref } from 'vue'
+import { ref, useTemplateRef } from 'vue'
// Simple fixed sidebar layout without responsive design
import ChartPreviewPanel from './components/ChartPreviewPanel.vue'
import ThemePanel from './components/ThemePanel.vue'
@@ -7,28 +7,28 @@ import { useLocalization } from
'./composables/useLocalization'
import { RadioGroup as VanRadioGroup, Radio as VanRadio, Row as VanRow, Col as
VanCol } from 'vant'
// Set up language control
-const { switchLanguage, currentLanguage, getAvailableLanguages } =
useLocalization()
+const { switchLanguage, currentLanguage, availableLanguages } =
useLocalization()
const currentLang = ref(currentLanguage)
-const availableLocales = getAvailableLanguages()
// Only show language selector in dev/preview mode
// Use import.meta.env.DEV to only show in development mode
-const showLanguageSelector = import.meta.env.DEV && availableLocales.length > 0
+const showLanguageSelector = import.meta.env.DEV || (window as
any).SHOW_LANGUAGE_SELECTOR
const onLanguageChange = (lang: string) => {
switchLanguage(lang)
}
// Get reference to chart preview panel
-const chartPreviewRef = ref<InstanceType<typeof ChartPreviewPanel> |
null>(null)
+const chartPreviewRef = useTemplateRef('chartPreviewRef')
</script>
<template>
- <div id="theme-builder">
+ <div class="theme-builder-body">
<!-- Language Selector - only shown in dev/preview mode -->
<div v-if="showLanguageSelector" class="language-selector">
<VanRadioGroup v-model="currentLang" direction="horizontal"
@change="onLanguageChange">
- <VanRadio name="en">English</VanRadio>
- <VanRadio name="zh">中文</VanRadio>
+ <VanRadio v-for="locale in availableLanguages" :name="locale.code"
:key="locale.code">
+ {{ locale.name }}
+ </VanRadio>
</VanRadioGroup>
</div>
@@ -47,9 +47,9 @@ const chartPreviewRef = ref<InstanceType<typeof
ChartPreviewPanel> | null>(null)
</template>
<style scoped>
-#theme-builder {
+.theme-builder-body {
width: 100%;
- height: 100vh;
+ height: 100%;
position: relative;
--van-button-default-height: auto;
--van-button-normal-padding: 8px 10px;
@@ -77,7 +77,7 @@ const chartPreviewRef = ref<InstanceType<typeof
ChartPreviewPanel> | null>(null)
}
.theme-config {
- height: 100vh;
+ height: 100%;
overflow-y: auto;
background-color: #ffffff;
border-right: 1px solid #ddd;
@@ -88,7 +88,7 @@ const chartPreviewRef = ref<InstanceType<typeof
ChartPreviewPanel> | null>(null)
}
.chart-container {
- height: 100vh;
+ height: 100%;
overflow: hidden;
background-color: #ffffff;
padding: 20px;
diff --git a/src/components/ChartPreviewPanel.vue
b/src/components/ChartPreviewPanel.vue
index e68123e..588b2d3 100644
--- a/src/components/ChartPreviewPanel.vue
+++ b/src/components/ChartPreviewPanel.vue
@@ -7,11 +7,12 @@
<div class="charts-grid">
<div
v-for="(config, index) in displayedCharts"
+ v-once
:key="config.type + index"
class="chart-item"
>
<div
- :ref="el => setChartRef(el, index)"
+ :ref="el => setChartDomRef(el, index)"
class="chart-container"
></div>
</div>
@@ -20,11 +21,12 @@
</template>
<script setup lang="ts">
-import { ref, onMounted, onUnmounted, nextTick, watch, computed } from 'vue'
+import { onMounted, onUnmounted, watch, computed, markRaw, nextTick } from
'vue'
import * as echarts from 'echarts'
import { getChartConfigs } from '../utils/chartConfigs'
import { useThemeStore } from '../stores/theme'
import type { ECharts } from 'echarts'
+import { useI18n } from 'vue-i18n'
// Debounce function to limit how often a function can be called
function debounce<T extends (...args: any[]) => any>(fn: T, delay: number):
(...args: Parameters<T>) => void {
@@ -43,86 +45,42 @@ function debounce<T extends (...args: any[]) => any>(fn: T,
delay: number): (...
}
// Initialize i18n
-// const { t } = useI18n() // Not currently being used
+const { locale } = useI18n()
const themeStore = useThemeStore()
-const chartInstances = ref<ECharts[]>([])
-const chartRefs = ref<(HTMLElement | null)[]>([])
+const chartInstances: ECharts[] = []
+const chartDoms: (HTMLElement | null)[] = []
// Dynamically generate charts based on seriesCnt
const displayedCharts = computed(() =>
getChartConfigs(themeStore.theme.seriesCnt))
-// Set chart ref
-function setChartRef(el: any, index: number) {
- if (el) {
- // Ensure the array is large enough
- while (chartRefs.value.length <= index) {
- chartRefs.value.push(null)
- }
- chartRefs.value[index] = el as HTMLElement
- }
-}
-
-// Register and apply current theme
-function registerCurrentTheme() {
- const currentTheme = themeStore.getEChartsTheme()
- echarts.registerTheme('customized', currentTheme)
-}
-
-// Initialize all charts
-function initializeCharts() {
- // Dispose existing charts
- chartInstances.value.forEach(chart => chart.dispose())
- chartInstances.value = []
-
- // Clear chart refs to ensure they match the new chart count
- chartRefs.value = []
-
- // Register current theme
- registerCurrentTheme()
-
- // Wait for DOM to update with new chart count
- nextTick(() => {
- // Create new chart instances
- displayedCharts.value.forEach((config, index) => {
- const container = chartRefs.value[index]
- if (container) {
- // Initialize chart with theme
- const chart = echarts.init(container, 'customized')
- chart.setOption(config.option)
- chartInstances.value.push(chart)
- }
- })
- })
+// Set chart DOM ref
+function setChartDomRef(el: any, index: number) {
+ chartDoms[index] = el as HTMLElement
}
// Original update charts implementation
function _updateChartsImpl() {
- if (chartInstances.value.length === 0) {
- initializeCharts()
- return
- }
-
- // If chart count doesn't match, reinitialize
- if (chartInstances.value.length !== displayedCharts.value.length) {
- initializeCharts()
- return
- }
+ // Dispose existing charts
+ chartInstances.forEach(chart => chart.dispose())
+ chartInstances.length = 0
- // Get current theme and register with unique ID to force refresh
+ // re-register theme with the same name
const currentTheme = themeStore.getEChartsTheme()
- const themeId = `customized-${Date.now()}`
+ const themeId = 'customized'
echarts.registerTheme(themeId, currentTheme)
+ const chartLocale = locale.value === 'zh' ? 'ZH' : 'EN'
// Recreate charts with new theme
- displayedCharts.value.forEach((config, index) => {
- const container = chartRefs.value[index]
- if (container && chartInstances.value[index]) {
- chartInstances.value[index].dispose()
- const chart = echarts.init(container, themeId)
+ nextTick(() => {
+ displayedCharts.value.forEach((config, index) => {
+ const container = chartDoms[index]
+ const chart = markRaw(echarts.init(container, themeId, {
+ locale: chartLocale
+ }))
chart.setOption(config.option)
- chartInstances.value[index] = chart
- }
+ chartInstances[index] = chart
+ })
})
}
@@ -133,30 +91,25 @@ defineExpose({
updateCharts
})
-// Watch for theme changes and automatically update charts
-watch(() => themeStore.theme, () => {
- if (!themeStore.isPauseChartUpdating.value) {
- nextTick(() => {
- updateCharts()
- })
- }
-}, { deep: true })
+// Watch for theme or locale changes and automatically update charts
+watch(() => [themeStore.theme, locale.value], updateCharts, { deep: true })
// Resize charts when window resizes
function handleResize() {
- chartInstances.value.forEach(chart => chart.resize())
+ chartInstances.forEach(chart => chart.resize())
}
+const debouncedHandleResize = debounce(handleResize, 100)
+
onMounted(() => {
- nextTick(() => {
- initializeCharts()
- })
- window.addEventListener('resize', handleResize)
+ updateCharts()
+ window.addEventListener('resize', debouncedHandleResize)
})
onUnmounted(() => {
- chartInstances.value.forEach(chart => chart.dispose())
- window.removeEventListener('resize', handleResize)
+ window.removeEventListener('resize', debouncedHandleResize)
+ chartInstances.forEach(chart => chart.dispose())
+ chartDoms.length = chartInstances.length = 0
})
</script>
diff --git a/src/components/ColorList.vue b/src/components/ColorList.vue
index 6ffb2d6..0809c8c 100644
--- a/src/components/ColorList.vue
+++ b/src/components/ColorList.vue
@@ -56,7 +56,6 @@
</template>
<script setup lang="ts">
-import { defineProps, defineEmits } from 'vue'
import { ColorPicker } from 'vue3-colorpicker'
import 'vue3-colorpicker/style.css'
diff --git a/src/components/ColorPicker.vue b/src/components/ColorPicker.vue
index d79e58d..d4dc245 100644
--- a/src/components/ColorPicker.vue
+++ b/src/components/ColorPicker.vue
@@ -23,7 +23,6 @@
</template>
<script setup lang="ts">
-import { defineProps, defineEmits } from 'vue'
import { ColorPicker } from 'vue3-colorpicker'
import 'vue3-colorpicker/style.css'
diff --git a/src/components/ThemePanel.vue b/src/components/ThemePanel.vue
index 3d93823..d69c55e 100644
--- a/src/components/ThemePanel.vue
+++ b/src/components/ThemePanel.vue
@@ -45,14 +45,17 @@
<!-- Theme Name and Series Count -->
<van-field
- v-model="themeName"
+ v-model.trim="themeName"
:label="$t('panel.themeName')"
:placeholder="$t('panel.themePlaceholder')"
/>
<van-field
- v-model.number="theme.seriesCnt"
+ v-model="theme.seriesCnt"
type="number"
+ inputmode="numeric"
+ :min="1"
+ :max="12"
:label="$t('panel.seriesCount')"
:placeholder="$t('panel.seriesPlaceholder')"
/>
@@ -63,8 +66,9 @@
<div class="theme-grid">
<div
v-for="(themeItem, index) in preDefinedThemes"
- :key="index"
+ :key="themeItem.name"
class="theme-item"
+ :class="themeStore.activePreDefinedThemeIndex.value === index
? 'active' : ''"
:style="{ backgroundColor: themeItem.background }"
:title="themeItem.name"
@click="selectPreDefinedTheme(index)"
@@ -463,7 +467,7 @@
</template>
<script setup lang="ts">
-import { ref } from 'vue'
+import { ref, useTemplateRef } from 'vue'
import { useThemeStore } from '../stores/theme'
import { PRE_DEFINED_THEMES } from '../stores/theme'
import ColorPicker from './ColorPicker.vue'
@@ -472,6 +476,7 @@ import type ChartPreviewPanel from './ChartPreviewPanel.vue'
import { downloadJsonFile, downloadJsFile } from '../utils/download'
import { showToast, showDialog } from 'vant'
import { useI18n } from 'vue-i18n'
+import * as echarts from 'echarts'
// Initialize i18n and localization
const { t } = useI18n()
@@ -484,7 +489,7 @@ const props = defineProps<Props>()
// Component state
const activeNames = ref(['functions'])
-const fileInput = ref<HTMLInputElement>()
+const fileInputRef = useTemplateRef('fileInput')
// Theme store
const themeStore = useThemeStore()
@@ -679,7 +684,7 @@ const downloadTheme = async () => {
try {
const themeConfig = themeStore.getEChartsTheme()
const jsContent = themeStore.getThemeJsFile()
- const filename = themeName.value || 'customized'
+ const filename = themeName.value &&
echarts.format.encodeHTML(themeName.value) || 'customized'
// Show format selection dialog using action sheet style
try {
@@ -688,7 +693,8 @@ const downloadTheme = async () => {
message: t('modals.formatSelectionMsg'),
showCancelButton: true,
confirmButtonText: t('modals.jsFormat'),
- cancelButtonText: t('modals.jsonFormat')
+ cancelButtonText: t('modals.jsonFormat'),
+ closeOnClickOverlay: true
})
// User chose JavaScript
@@ -709,48 +715,48 @@ const downloadTheme = async () => {
}
const showUsageInstructions = (format: 'js' | 'json', filename: string) => {
- const themeNameDisplay = themeName.value || 'customized'
-
if (format === 'js') {
showDialog({
title: t('modals.jsUsageTitle'),
message: `<div style="text-align: left; padding: 5px 0;">
- <ol style="margin: 0; line-height: 1">
- <li>${t('modals.jsUsageStep1').replace('{filename}', `<code
style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-family:
Monaco, monospace;">${filename}.js</code>`)}</li>
+ <ol style="margin: 0; line-height: 1; list-style: inside decimal;">
+ <li>${t('modals.jsUsageStep1').replace('__filename__', `<code
style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-family:
Monaco, monospace;">${filename}.js</code>`)}</li>
<li>${t('modals.jsUsageStep2')}<br/><code style="background:
#f0f0f0; padding: 4px 8px; border-radius: 3px; font-family: Monaco, monospace;
display: inline-block; margin-top: 6px;"><script
src="${filename}.js"></script></code></li>
- <li>${t('modals.jsUsageStep3')}<br/><code style="background:
#f0f0f0; padding: 4px 8px; border-radius: 3px; font-family: Monaco, monospace;
display: inline-block; margin-top: 6px;">echarts.init(dom,
'${themeNameDisplay}')</code></li>
+ <li>${t('modals.jsUsageStep3')}<br/><code style="background:
#f0f0f0; padding: 4px 8px; border-radius: 3px; font-family: Monaco, monospace;
display: inline-block; margin-top: 6px;">echarts.init(dom,
'${filename}')</code></li>
</ol>
- <p style="margin: 0; color: #666; font-size: 14px; line-height: 1;
background: #f8f9fa; padding: 10px; border-radius: 4px; border-left: 3px solid
#1989fa;">${t('modals.jsUsageTip')}</p>
+ <p style="margin: 0; color: #666; font-size: 14px; line-height: 1;
background: #f8f9fa; padding: 10px; border-radius: 4px; border-left: 3px solid
var(--van-primary-color);">${t('modals.jsUsageTip')}</p>
</div>`,
allowHtml: true,
- confirmButtonText: t('common.ok')
+ confirmButtonText: t('common.ok'),
+ closeOnClickOverlay: true
})
} else {
showDialog({
title: t('modals.jsonUsageTitle'),
message: `<div style="text-align: left; padding: 5px 0;">
- <ol style="margin: 0; line-height: 1">
- <li>${t('modals.jsonUsageStep1').replace('{filename}', `<code
style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-family:
Monaco, monospace;">${filename}.json</code>`)}</li>
+ <ol style="margin: 0; line-height: 1; list-style: inside decimal;">
+ <li>${t('modals.jsonUsageStep1').replace('__filename__', `<code
style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-family:
Monaco, monospace;">${filename}.json</code>`)}</li>
<li>${t('modals.jsonUsageStep2')}<br/><code style="background:
#f0f0f0; padding: 4px 8px; border-radius: 3px; font-family: Monaco, monospace;
display: inline-block; margin-top: 6px;">const obj =
JSON.parse(data)</code></li>
- <li>${t('modals.jsonUsageStep3')}<br/><code style="background:
#f0f0f0; padding: 4px 8px; border-radius: 3px; font-family: Monaco, monospace;
display: inline-block; margin-top:
6px;">echarts.registerTheme('${themeNameDisplay}', obj)</code></li>
- <li>${t('modals.jsonUsageStep4')}<br/><code style="background:
#f0f0f0; padding: 4px 8px; border-radius: 3px; font-family: Monaco, monospace;
display: inline-block; margin-top: 6px;">echarts.init(dom,
'${themeNameDisplay}')</code></li>
+ <li>${t('modals.jsonUsageStep3')}<br/><code style="background:
#f0f0f0; padding: 4px 8px; border-radius: 3px; font-family: Monaco, monospace;
display: inline-block; margin-top: 6px;">echarts.registerTheme('${filename}',
obj)</code></li>
+ <li>${t('modals.jsonUsageStep4')}<br/><code style="background:
#f0f0f0; padding: 4px 8px; border-radius: 3px; font-family: Monaco, monospace;
display: inline-block; margin-top: 6px;">echarts.init(dom,
'${filename}')</code></li>
</ol>
- <p style="margin: 0; color: #666; font-size: 14px; line-height: 1;
background: #f8f9fa; padding: 10px; border-radius: 4px; border-left: 3px solid
#1989fa;">${t('modals.jsonUsageTip')}</p>
+ <p style="margin: 0; color: #666; font-size: 14px; line-height: 1;
background: #f8f9fa; padding: 10px; border-radius: 4px; border-left: 3px solid
var(--van-primary-color);">${t('modals.jsonUsageTip')}</p>
</div>`,
allowHtml: true,
- confirmButtonText: t('common.ok')
+ confirmButtonText: t('common.ok'),
+ closeOnClickOverlay: true
})
}
}
const importConfig = () => {
- fileInput.value?.click()
+ fileInputRef.value?.click()
}
const exportConfig = async () => {
try {
const configData = themeStore.getThemeConfigForDownload()
- const filename = `${themeName.value || 'customized'}.project`
+ const filename = `${themeName.value &&
echarts.format.encodeHTML(themeName.value) || 'customized'}.project`
downloadJsonFile(configData, filename)
@@ -817,6 +823,10 @@ const openSourceCode = () => {
}
const selectPreDefinedTheme = async (index: number) => {
+ if (themeStore.activePreDefinedThemeIndex.value === index) {
+ // Already selected
+ return
+ }
try {
await themeStore.loadPreDefinedTheme(index)
@@ -976,9 +986,10 @@ const handleFileImport = async (event: Event) => {
gap: 2px;
}
-.theme-item:hover {
- border-color: #1989fa;
- box-shadow: 0 2px 8px rgba(25, 137, 250, 0.15);
+.theme-item:hover,
+.theme-item.active {
+ border-color: var(--van-primary-color);
+ box-shadow: 0 0 4px var(--van-primary-color);
}
.color-dot {
@@ -1158,7 +1169,7 @@ const handleFileImport = async (event: Event) => {
}
:global(.modal-body a) {
- color: #1989fa;
+ color: var(--van-primary-color);
text-decoration: none;
}
diff --git a/src/composables/useLocalization.ts
b/src/composables/useLocalization.ts
index 8f55899..6400f05 100644
--- a/src/composables/useLocalization.ts
+++ b/src/composables/useLocalization.ts
@@ -1,5 +1,5 @@
import { useI18n } from 'vue-i18n'
-import { setLocale, getCurrentLocale, getAvailableLocales } from '../i18n'
+import { setLocale, getCurrentLocale, availableLocales } from '../i18n'
/**
* Localization composable
@@ -21,16 +21,6 @@ export function useLocalization() {
*/
const currentLanguage = getCurrentLocale()
- /**
- * Get available languages list
- */
- const availableLanguages = getAvailableLocales()
-
- /**
- * Get available languages list function
- */
- const getAvailableLanguages = () => getAvailableLocales()
-
/**
* Check if current language matches specified language
* @param lang Language code
@@ -66,8 +56,7 @@ export function useLocalization() {
locale,
switchLanguage,
currentLanguage,
- availableLanguages,
- getAvailableLanguages,
+ availableLanguages: availableLocales,
isLanguage,
formatDate,
formatNumber
diff --git a/src/entries/en.ts b/src/entries/en.ts
deleted file mode 100644
index ca3adfd..0000000
--- a/src/entries/en.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-// English Entry Point
-import { createApp } from 'vue'
-import {
- Col,
- Row,
- Collapse,
- CollapseItem,
- Field,
- Button,
- Icon,
- Checkbox,
- RadioGroup,
- Radio
-} from 'vant'
-import 'vant/lib/index.css'
-import '../style.css'
-import App from '../App.vue'
-import i18n from '../i18n'
-import { setLocale } from '../i18n'
-
-// Force English locale
-setLocale('en')
-
-const app = createApp(App)
-app.use(i18n)
-app.use(Col)
-app.use(Row)
-app.use(Collapse)
-app.use(CollapseItem)
-app.use(Field)
-app.use(Button)
-app.use(Icon)
-app.use(Checkbox)
-app.use(RadioGroup)
-app.use(Radio)
-app.mount('#theme-builder')
diff --git a/src/entries/zh.ts b/src/entries/zh.ts
deleted file mode 100644
index ae51117..0000000
--- a/src/entries/zh.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-// Chinese Entry Point
-import { createApp } from 'vue'
-import {
- Col,
- Row,
- Collapse,
- CollapseItem,
- Field,
- Button,
- Icon,
- Checkbox,
- RadioGroup,
- Radio
-} from 'vant'
-import 'vant/lib/index.css'
-import '../style.css'
-import App from '../App.vue'
-import i18n from '../i18n'
-import { setLocale } from '../i18n'
-
-// Force Chinese locale
-setLocale('zh')
-
-const app = createApp(App)
-app.use(i18n)
-app.use(Col)
-app.use(Row)
-app.use(Collapse)
-app.use(CollapseItem)
-app.use(Field)
-app.use(Button)
-app.use(Icon)
-app.use(Checkbox)
-app.use(RadioGroup)
-app.use(Radio)
-app.mount('#theme-builder')
diff --git a/src/i18n.ts b/src/i18n.ts
index f7d0ff8..1267e4f 100644
--- a/src/i18n.ts
+++ b/src/i18n.ts
@@ -2,18 +2,23 @@ import { createI18n } from 'vue-i18n'
import en from './locales/en.json'
import zh from './locales/zh.json'
+// Available languages list
+export const availableLocales = Object.freeze([
+ { code: 'en', name: 'English' },
+ { code: 'zh', name: '简体中文' }
+])
+
// Get saved language setting from localStorage, default to Chinese
const getStoredLanguage = (): string => {
- const stored = localStorage.getItem('echarts-theme-builder-locale')
- if (stored && ['en', 'zh'].includes(stored)) {
+ const stored = import.meta.env.DEV
+ ? localStorage.getItem('echarts-theme-builder-locale')
+ : ((window as any).EC_WWW_LANG === 'zh' ? 'zh' : 'en')
+ if (stored && availableLocales.some(l => l.code === stored)) {
return stored
}
// Auto-select based on browser language
- const browserLang = navigator.language.toLowerCase()
- if (browserLang.startsWith('zh')) {
- return 'zh'
- }
- return 'en'
+ const browserLang: string = document.documentElement.lang ||
navigator.language || (navigator as any).browserLanguage
+ return browserLang && browserLang.toLowerCase().startsWith('zh') ? 'zh' :
'en'
}
const i18n = createI18n({
@@ -28,9 +33,11 @@ const i18n = createI18n({
// Save language setting to localStorage
export const setLocale = (locale: string) => {
- if (['en', 'zh'].includes(locale)) {
+ if (availableLocales.some(l => l.code === locale)) {
i18n.global.locale.value = locale as 'en' | 'zh'
- localStorage.setItem('echarts-theme-builder-locale', locale)
+ if (import.meta.env.DEV) {
+ localStorage.setItem('echarts-theme-builder-locale', locale)
+ }
}
}
@@ -39,18 +46,4 @@ export const getCurrentLocale = () => {
return i18n.global.locale.value
}
-// Get available languages list
-export const getAvailableLocales = () => {
- // 在 release 模式下不显示语言切换选项
- if (import.meta.env.VITE_MODE === 'release') {
- return []
- }
-
- // 在开发/预览模式下显示语言切换选项
- return [
- { code: 'en', name: 'English' },
- { code: 'zh', name: '中文' }
- ]
-}
-
export default i18n
diff --git a/src/locales/en.json b/src/locales/en.json
index 16c8231..8a86800 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -148,7 +148,7 @@
"whatIs": "What is the Theme Builder?",
"whatIsDesc1": "A \"theme\" is a style abstraction for ECharts charts,
used to unify the style of multiple charts. Using the online theme builder, you
can quickly and intuitively generate theme configuration files and use
customized theme styles in ECharts.",
"whatIsDesc2": "Based on this theme, you can still use
<code>setOption</code> to override or set theme styles.",
- "whatIsDesc3": "ECharts officially provides themes such as
<code>default</code>, <code>infographic</code>, <code>shine</code>,
<code>roma</code>, <code>macarons</code>, <code>vintage</code>, etc., which can
be <a href=\"http://echarts.baidu.com/download-theme.html\"
target=\"_blank\">downloaded</a> for use.",
+ "whatIsDesc3": "ECharts officially provides themes such as
<code>default</code>, <code>infographic</code>, <code>shine</code>,
<code>roma</code>, <code>macarons</code>, <code>vintage</code>, etc., which can
be <a href=\"https://echarts.apache.org/download-theme.html\"
target=\"_blank\">downloaded</a> for use.",
"importExport": "Import and Export",
"importExportDesc": "For convenient secondary modifications, our theme
builder supports importing and exporting configuration items. The exported JSON
file is only used for importing in this tool and cannot be directly registered
as a theme in ECharts."
},
@@ -158,8 +158,8 @@
"jsonFormat": "JSON File",
"jsUsageTitle": "JavaScript Theme File Usage",
"jsonUsageTitle": "JSON Theme File Usage",
- "jsUsageStep1": "Save the downloaded {filename} file to your project",
- "jsonUsageStep1": "Save the downloaded {filename} file to your project",
+ "jsUsageStep1": "Save the downloaded __filename__ file to your project",
+ "jsonUsageStep1": "Save the downloaded __filename__ file to your project",
"jsUsageStep2": "Include this file in your HTML:",
"jsonUsageStep2": "Read and parse the JSON file:",
"jsUsageStep3": "Use the theme when creating a chart:",
diff --git a/src/locales/zh.json b/src/locales/zh.json
index b165897..0132c1b 100644
--- a/src/locales/zh.json
+++ b/src/locales/zh.json
@@ -148,7 +148,7 @@
"whatIs": "主题在线构建工具是什么?",
"whatIsDesc1": "\"主题\"是 ECharts
图表的风格抽象,用于统一多个图表的风格样式。使用主题在线构建工具,可以快速直观地生成主题配置文件,并在 ECharts 中使用自定义的主题样式。",
"whatIsDesc2": "在此主题的基础上,你仍然可以使用 <code>setOption</code> 覆盖或设置主题样式。",
- "whatIsDesc3": "ECharts 官方提供
<code>default</code>、<code>infographic</code>、<code>shine</code>、<code>roma</code>、<code>macarons</code>、<code>vintage</code>
等主题,可供<a href=\"http://echarts.baidu.com/download-theme.html\"
target=\"_blank\">下载</a>使用。",
+ "whatIsDesc3": "ECharts 官方提供
<code>default</code>、<code>infographic</code>、<code>shine</code>、<code>roma</code>、<code>macarons</code>、<code>vintage</code>
等主题,可供<a href=\"https://echarts.apache.org/download-theme.html\"
target=\"_blank\">下载</a>使用。",
"importExport": "导入、导出",
"importExportDesc": "为了便于二次修改,我们的主题构建工具支持导入、导出配置项,导出的 JSON
文件仅用于在本工具中导入使用,而不能直接作为主题在 ECharts 中注册。"
},
@@ -158,8 +158,8 @@
"jsonFormat": "JSON 文件",
"jsUsageTitle": "JavaScript 主题文件使用方法",
"jsonUsageTitle": "JSON 主题文件使用方法",
- "jsUsageStep1": "将下载的 {filename} 文件保存到项目中",
- "jsonUsageStep1": "将下载的 {filename} 文件保存到项目中",
+ "jsUsageStep1": "将下载的 __filename__ 文件保存到项目中",
+ "jsonUsageStep1": "将下载的 __filename__ 文件保存到项目中",
"jsUsageStep2": "在 HTML 中引入此文件:",
"jsonUsageStep2": "读取 JSON 文件并解析:",
"jsUsageStep3": "创建图表时使用主题:",
diff --git a/src/main.ts b/src/main.ts
index 86df650..eb2f6ae 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,3 +1,5 @@
+import 'vant/lib/index.css'
+import './style.css'
import { createApp } from 'vue'
import {
Col,
@@ -11,8 +13,6 @@ import {
RadioGroup,
Radio
} from 'vant'
-import 'vant/lib/index.css'
-import './style.css'
import App from './App.vue'
import i18n from './i18n'
@@ -28,4 +28,4 @@ app.use(Icon)
app.use(Checkbox)
app.use(RadioGroup)
app.use(Radio)
-app.mount('#theme-builder')
+app.mount('#theme-builder-app')
diff --git a/src/stores/theme.ts b/src/stores/theme.ts
index 97e1550..4c0b937 100644
--- a/src/stores/theme.ts
+++ b/src/stores/theme.ts
@@ -1,9 +1,9 @@
-import { ref, reactive } from 'vue'
+import { ref, reactive, markRaw } from 'vue'
import type { ThemeData, PreDefinedTheme } from '../types/theme'
import { generateEChartsTheme, generateThemeJsFile,
generateThemeConfigForDownload } from '../utils/themeGenerator'
// Predefined themes configuration
-export const PRE_DEFINED_THEMES: PreDefinedTheme[] = [
+export const PRE_DEFINED_THEMES: PreDefinedTheme[] = markRaw([
{
name: 'v5',
background: 'rgba(0, 0, 0, 0)',
@@ -121,7 +121,7 @@ export const PRE_DEFINED_THEMES: PreDefinedTheme[] = [
'#7cb4cc'
]
}
-]
+])
// Default theme axes configuration
const createDefaultAxes = () => {
@@ -190,7 +190,7 @@ export const createDefaultTheme = (): ThemeData => {
mapAreaColorE: 'rgba(255,215,0,0.8)',
axes,
axisSeperateSetting: true,
- axis: [axes[0]],
+ axis: [axes[0]!],
toolboxColor: '#999',
toolboxEmphasisColor: '#666',
tooltipAxisColor: '#ccc',
@@ -223,18 +223,29 @@ let themeStoreInstance: ReturnType<typeof
createThemeStore> | null = null
const createThemeStore = () => {
const theme = reactive<ThemeData>(createDefaultTheme())
const themeName = ref('customized')
- const isPauseChartUpdating = ref(false)
const chartDisplay = reactive({
background: '#fff',
title: '#000'
})
const resetTheme = () => {
+ activePreDefinedThemeIndex.value = null
Object.assign(theme, createDefaultTheme())
themeName.value = 'customized'
}
+ const activePreDefinedThemeIndex = ref<number | null>(null)
+ let definedThemeLoadAbortController: AbortController | null = null
const loadPreDefinedTheme = async (index: number) => {
+ if (activePreDefinedThemeIndex.value === index) {
+ return
+ }
+
+ activePreDefinedThemeIndex.value = index
+
+ definedThemeLoadAbortController?.abort()
+ definedThemeLoadAbortController = new AbortController()
+
const preTheme = PRE_DEFINED_THEMES[index]
if (!preTheme) {
console.error('No theme found at index:', index)
@@ -243,7 +254,9 @@ const createThemeStore = () => {
try {
// Load the complete theme configuration from JSON file
- const response = await fetch(`/themes/${preTheme.name}.json`)
+ const response = await
fetch(`${import.meta.env.BASE_URL}themes/${preTheme.name}.json`, {
+ signal: definedThemeLoadAbortController.signal
+ })
if (!response.ok) {
throw new Error(`Failed to load theme: ${preTheme.name}`)
}
@@ -288,7 +301,7 @@ const createThemeStore = () => {
if (theme.axisSeperateSetting) {
theme.axis = theme.axes
} else {
- theme.axis = [theme.axes[0]]
+ theme.axis = [theme.axes[0]!]
}
}
@@ -332,9 +345,9 @@ const createThemeStore = () => {
return {
theme,
themeName,
- isPauseChartUpdating,
chartDisplay,
resetTheme,
+ activePreDefinedThemeIndex,
loadPreDefinedTheme,
updateAxisSetting,
importTheme,
diff --git a/src/style.css b/src/style.css
index 1b18993..9ae387e 100644
--- a/src/style.css
+++ b/src/style.css
@@ -1,5 +1,10 @@
-body {
- width: 100%;
+#theme-builder-app {
height: 100%;
- margin: 0;
+ overflow: auto;
+}
+
+:root, :host {
+ --van-primary-color: #ff6d8f;
+ /* inherit font from parent page */
+ --van-base-font: inherit;
}
diff --git a/src/utils/themeGenerator.ts b/src/utils/themeGenerator.ts
index 8b162c5..c3f8061 100644
--- a/src/utils/themeGenerator.ts
+++ b/src/utils/themeGenerator.ts
@@ -58,7 +58,7 @@ export function generateEChartsTheme(themeData: ThemeData) {
axisIndex = axisType
}
- const axisData = themeData.axes[axisIndex]
+ const axisData = themeData.axes[axisIndex]!
return {
axisLine: {
show: axisData.axisLineShow,
diff --git a/vite.config.ts b/vite.config.ts
index 629541c..934877e 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,145 +1,72 @@
-import { defineConfig, type Plugin } from 'vite'
+import path from 'node:path'
+import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
-import path from 'path'
-import fs from 'fs'
-import fse from 'fs-extra'
+import { viteStaticCopy } from 'vite-plugin-static-copy'
-const SUPPORTED_LANGUAGES: string[] = ['en', 'zh'];
-const OUTPUT_DIR: string = 'app';
-
-// Custom plugin to handle HTML output for different languages
-function createHtmlOutputPlugin(): Plugin {
+export default defineConfig((env) => {
+ const isBuildForApp = env.mode === 'app'
return {
- name: 'html-output-plugin',
- enforce: 'post',
- apply: 'build',
- closeBundle: async () => {
- const distDir = path.resolve(__dirname, 'dist');
- const publicDir = path.resolve(__dirname, 'public');
-
- // Create language directories if they don't exist
- for (const lang of SUPPORTED_LANGUAGES) {
- const langDir = path.join(OUTPUT_DIR, lang);
- const assetDir = path.join(langDir, 'theme-builder');
- const themesDir = path.join(assetDir, 'themes');
-
- // Ensure directories exist and clean any existing assets
- await fse.ensureDir(langDir);
- await fse.ensureDir(assetDir);
-
- // Clean up existing JS and CSS files before copying new ones
- const existingFiles = await fse.readdir(assetDir);
- for (const file of existingFiles) {
- if (file.endsWith('.js') || file.endsWith('.css')) {
- await fse.remove(path.join(assetDir, file));
+ plugins: [
+ vue({
+ template: {
+ transformAssetUrls: {
+ base: '/src',
+ includeAbsolute: false,
}
}
-
- await fse.ensureDir(themesDir);
-
- // Find JS and CSS files in the dist directory
- const files = await fse.readdir(distDir);
- const jsFiles = files.filter(file => file.endsWith('.js'));
- const cssFiles = files.filter(file => file.endsWith('.css'));
-
- // Read the HTML file to extract scripts and links to include in
body.html
- if (fs.existsSync(path.join(distDir, 'index.html'))) {
- const htmlContent = await fse.readFile(path.join(distDir,
'index.html'), 'utf-8');
-
- // Extract only the content within the body tag
- let bodyContent =
htmlContent.match(/<body[^>]*>([\s\S]*)<\/body>/i)?.[1] || '';
-
- // Get all script and link tags from head
- const headScripts =
(htmlContent.match(/<script[^>]*src="\.\/([^"]+)"[^>]*><\/script>/g) || [])
- .map(script => script.replace(/src="\.\//g,
'src="theme-builder/'));
-
- const headLinks =
(htmlContent.match(/<link[^>]*href="\.\/([^"]+)"[^>]*>/g) || [])
- .map(link => link.replace(/href="\.\//g, 'href="theme-builder/'));
-
- // Create a complete body.html with necessary script and link tags
- let finalContent = [...headLinks, ...headScripts,
bodyContent.trim()].join('\n');
-
- // Replace any references to themes in the content
- finalContent = finalContent.replace(/(['"])\.?\/themes\//g,
'$1./theme-builder/themes/');
-
- // Write the content to body.html
- await fse.writeFile(path.join(langDir, 'body.html'), finalContent);
- }
-
- // Copy JS and CSS files to the theme-builder directory
- for (const jsFile of jsFiles) {
- await fse.copy(
- path.join(distDir, jsFile),
- path.join(assetDir, jsFile)
- );
- }
-
- for (const cssFile of cssFiles) {
- await fse.copy(
- path.join(distDir, cssFile),
- path.join(assetDir, cssFile)
- );
- }
-
- // Copy themes from public directory
- if (fs.existsSync(path.join(publicDir, 'themes'))) {
- await fse.copy(
- path.join(publicDir, 'themes'),
- themesDir
- );
+ }),
+ isBuildForApp && viteStaticCopy({
+ targets: [
+ {
+ src: 'public/*',
+ dest: './theme-builder'
+ }
+ ]
+ }),
+ {
+ // PENDING: Or set build.rollupOptions.output.format to 'umd' (will
not extract css to separate file)
+ name: 'remove-echarts-import',
+ apply: 'build',
+ generateBundle(_, bundle) {
+ const reg = /import.*as(.*)from.*['"]echarts['"];?/g
+ for (const [_fileName, chunkInfo] of Object.entries(bundle)) {
+ if (chunkInfo.type === 'chunk') {
+ chunkInfo.code = chunkInfo.code.replace(reg, (_match, p1) => {
+ p1 = p1.trim();
+ return `const
${p1}=window.echarts;!${p1}&&console.error("ECharts is not loaded!");`
+ })
+ }
+ }
}
}
-
- // Clean up the original dist directory
- // await fse.remove(distDir);
- }
- };
-}
-
-// Custom plugin to rewrite theme paths in JS files
-function createThemePathRewritePlugin(): Plugin {
- return {
- name: 'theme-path-rewrite',
- transform(code, id) {
- if (id.endsWith('.js') || id.endsWith('.ts')) {
- // Replace theme path references
- return code.replace(/(['"`])\.?\/themes\//g,
'$1./theme-builder/themes/');
- }
- return code;
- }
- };
-}
-
-export default defineConfig({
- plugins: [
- vue({
- template: {
- transformAssetUrls: {
- base: '/src',
- includeAbsolute: false,
+ ],
+ build: {
+ outDir: isBuildForApp ? 'app' : 'dist', // Target different output
directories based on the build mode
+ emptyOutDir: true,
+ assetsDir: isBuildForApp ? './theme-builder' : '.',
+ // public dir will be copied via vite-plugin-static-copy when building
for app
+ copyPublicDir: !isBuildForApp,
+ rollupOptions: {
+ external: ['echarts'],
+ output: {
+ format: 'module',
+ globals: {
+ echarts: 'echarts'
+ }
},
+ input: path.resolve(__dirname, `${isBuildForApp ? 'app' :
'index'}.html`)
}
- }),
- createThemePathRewritePlugin(),
- createHtmlOutputPlugin()
- ],
- build: {
- outDir: 'dist', // Temporary build directory
- emptyOutDir: true,
- assetsDir: '.', // This will ensure assets are placed at the root level of
the output directory
- rollupOptions: {
- output: {
- // Customize output file names
- entryFileNames: '[name]-[hash].js',
- chunkFileNames: '[name]-[hash].js',
- assetFileNames: '[name]-[hash].[ext]'
+ },
+ base: './', // Make sure assets use relative paths
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, './src')
}
- }
- },
- base: './', // Make sure assets use relative paths
- resolve: {
- alias: {
- '@': path.resolve(__dirname, './src')
+ },
+ define: {
+ 'import.meta.env.VITE_EXTERNAL_ECHARTS_SCRIPT': isBuildForApp
+ ? `""`
+ : `"<script
src=\\"https://echarts.apache.org/en/js/vendors/echarts/dist/echarts.min.js\\"></script>"`
}
}
});
diff --git a/zh/body.html b/zh/body.html
deleted file mode 100644
index 42fe349..0000000
--- a/zh/body.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<div id="theme-builder"></div>
-<script type="module" src="../src/entries/zh.ts"></script>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]