feat:修复主题

This commit is contained in:
zyronon
2025-08-03 16:32:20 +08:00
parent 102f5fbc1f
commit 6d9fbf234f
31 changed files with 272 additions and 1679 deletions

View File

@@ -24,8 +24,6 @@
"element-plus": "^2.10.3",
"file-saver": "^2.0.5",
"git-last-commit": "^1.0.1",
"hover.css": "^2.3.2",
"jquery": "^3.7.1",
"libarchive-wasm": "^1.2.0",
"localforage": "^1.10.0",
"lodash-es": "^4.17.21",
@@ -35,7 +33,6 @@
"sentence-splitter": "^4.4.1",
"string-comparison": "^1.3.0",
"tesseract.js": "^4.1.4",
"vant": "^4.9.20",
"vue": "^3.5.17",
"vue-activity-calendar": "^1.2.2",
"vue-i18n": "^9.14.4",
@@ -46,7 +43,7 @@
"@iconify/vue": "^4.3.0",
"@types/file-saver": "^2.0.7",
"@types/lodash-es": "^4.17.12",
"@unocss/postcss": "^0.60.4",
"@unocss/postcss": "^66.4.0",
"@vitejs/plugin-vue": "^6.0.0",
"@vitejs/plugin-vue-jsx": "^5.0.1",
"@vue/compiler-sfc": "^3.5.17",
@@ -59,7 +56,7 @@
"sass": "^1.89.2",
"tslib": "^2.8.1",
"typescript": "^5.8.3",
"unocss": "^66.3.3",
"unocss": "^66.4.0",
"unplugin-auto-import": "^0.16.7",
"unplugin-vue-components": "^0.25.2",
"unplugin-vue-macros": "^2.14.5",

426
pnpm-lock.yaml generated
View File

@@ -38,12 +38,6 @@ importers:
git-last-commit:
specifier: ^1.0.1
version: 1.0.1
hover.css:
specifier: ^2.3.2
version: 2.3.2
jquery:
specifier: ^3.7.1
version: 3.7.1
libarchive-wasm:
specifier: ^1.2.0
version: 1.2.0
@@ -71,9 +65,6 @@ importers:
tesseract.js:
specifier: ^4.1.4
version: 4.1.4
vant:
specifier: ^4.9.20
version: 4.9.20(vue@3.5.17(typescript@5.8.3))
vue:
specifier: ^3.5.17
version: 3.5.17(typescript@5.8.3)
@@ -100,8 +91,8 @@ importers:
specifier: ^4.17.12
version: 4.17.12
'@unocss/postcss':
specifier: ^0.60.4
version: 0.60.4(postcss@8.5.6)
specifier: ^66.4.0
version: 66.4.0(postcss@8.5.6)
'@vitejs/plugin-vue':
specifier: ^6.0.0
version: 6.0.0(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2))(vue@3.5.17(typescript@5.8.3))
@@ -117,6 +108,9 @@ importers:
cz-conventional-changelog:
specifier: ^3.3.0
version: 3.3.0(@types/node@24.0.11)(typescript@5.8.3)
daisyui:
specifier: ^5.0.50
version: 5.0.50
esm:
specifier: ^3.2.25
version: 3.2.25
@@ -139,8 +133,8 @@ importers:
specifier: ^5.8.3
version: 5.8.3
unocss:
specifier: ^66.3.3
version: 66.3.3(postcss@8.5.6)(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2))(vue@3.5.17(typescript@5.8.3))
specifier: ^66.4.0
version: 66.4.0(postcss@8.5.6)(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2))
unplugin-auto-import:
specifier: ^0.16.7
version: 0.16.7(@vueuse/core@9.13.0(vue@3.5.17(typescript@5.8.3)))(rollup@4.44.2)
@@ -888,117 +882,92 @@ packages:
'@types/web-bluetooth@0.0.16':
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
'@unocss/astro@66.3.3':
resolution: {integrity: sha512-q26EfadSMmEXZpWDKsJF9anBCfhYDmWljVpDZ2Wo8K48IbZMUXrWfiAiUc6ijE/A/rADfHk8bp3a3GE01t3I9A==}
'@unocss/astro@66.4.0':
resolution: {integrity: sha512-DDc22MhzS5SD7LXiJetNl/WglkBkQEKDDzaay4rUpvINdRu3eME1ISdgUBel4jkchSSenTt2AZlD9l6CecFXEw==}
peerDependencies:
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0
peerDependenciesMeta:
vite:
optional: true
'@unocss/cli@66.3.3':
resolution: {integrity: sha512-U0HoDcwi/DetqP5zDT3dfxG94pC3TI0PfxmpdTfPY7xEylIdLbV89fb70CvJVysDSQJIuw6TYwqS1ZlHoYNKTA==}
'@unocss/cli@66.4.0':
resolution: {integrity: sha512-zSdFHrYwgDuHTklFXWnWqp5dJq+aDOFxCZHK0M3hnZtEiaSgMce8Fdje9hOOi/FtCuKr1/BHLyjD1Vj240PVOw==}
engines: {node: '>=14'}
hasBin: true
'@unocss/config@0.60.4':
resolution: {integrity: sha512-ri9P2+YztD5JdPYSLiNjcLf6NgoBbwJDVutP/tQnfYYrE72DQ+j+4vepyxEBa1YaH/X4qsmLJCj+2tI/ufIiog==}
'@unocss/config@66.4.0':
resolution: {integrity: sha512-0H0dd5sWuFg9Z7oN+nGaL9UV4KitNuEcFcVVMUxPW3l+j3BKGMy6B+2jNS2+ezmpJoh5jaaL/fm5loYvOvaATA==}
engines: {node: '>=14'}
'@unocss/config@66.3.3':
resolution: {integrity: sha512-D/UxnAmkabapqWU4tF85dWWhNfCUyNutWmd4AD2VsQRZOykufJedLV74r3Z3XhoPJn4IGr3BKZm5/rflf5viDg==}
engines: {node: '>=14'}
'@unocss/core@66.4.0':
resolution: {integrity: sha512-vrfK8i3EwbKDbrhmR5lJQQltU1U0SvPqr2XVTHqZdCdzTUsg73I4NqFSiadt486i421C8BfTa2MPNHBnv35RuA==}
'@unocss/core@0.60.4':
resolution: {integrity: sha512-6tz8KTzC30oB0YikwRQoIpJ6Y6Dg+ZiK3NfCIsH+UX11bh2J2M53as2EL/5VQCqtiUn3YP0ZEzR2d1AWX78RCA==}
'@unocss/extractor-arbitrary-variants@66.4.0':
resolution: {integrity: sha512-P4bAb/oQ14TP7KZE4jxj4jcgCROkj8Ndnm3WKAmX+gwZLeAATjF0dn40EqLzmhLkXQYttp1DIEyvV77hsDZZOw==}
'@unocss/core@66.3.3':
resolution: {integrity: sha512-6WFLd92TJelVQARtCGaF+EgEoHKIVe43gkGXVoWILu0HUDRWdhv+cpcyX0RTJV22Y976AxeneU7/zmhAh+CXNg==}
'@unocss/inspector@66.4.0':
resolution: {integrity: sha512-wYWvvoiycl06SSLMKD1PAshSRzXnAd1Zk3F3CfviJUVKrp5ugLSbzZe+mnYKpNWTrNwfCNG69YhdsJnSdkb35Q==}
'@unocss/extractor-arbitrary-variants@66.3.3':
resolution: {integrity: sha512-TXzjH6FcITQ8V2x7ETHgVOlAHf3ll/ysxL+W4fMROm8jP/o7jvsg36tRfOwU0sDGo/qoCPux82ix9e6/JW0oqQ==}
'@unocss/inspector@66.3.3':
resolution: {integrity: sha512-NsK1WRWez2Mzk4+ophtBdXel8nGaPkIDa9lYSFMdKLF/1jNW23txeEL8CsD6/CK8K0BsR11rhLKhUrzyrjfBSQ==}
'@unocss/postcss@0.60.4':
resolution: {integrity: sha512-mHha4BoOpCWRRL5EFJqsj+BiYxOBPXUZDFbSWmA8oAMBwcA/yqtnaRF2tqI9CK+CDfhmtbYF64KdTLh9pf6BvQ==}
'@unocss/postcss@66.4.0':
resolution: {integrity: sha512-MX6hFo54+tiysvstHKhNP1nQabqKzXDzdX/6Ctqhj++cL/yRfz6vqcv8MSbfBQDciiTin0ikDytBYik0pRgENQ==}
engines: {node: '>=14'}
peerDependencies:
postcss: ^8.4.21
'@unocss/postcss@66.3.3':
resolution: {integrity: sha512-VKq+BtfPIZbLeAeZFprtKZJAyFBOqA8qpQm+vmWBiBia70JzkwfF2SMNIHiGt022yRo9ZmjnI9uRTxSzqXUsUQ==}
engines: {node: '>=14'}
peerDependencies:
postcss: ^8.4.21
'@unocss/preset-attributify@66.4.0':
resolution: {integrity: sha512-iH/ZwbAJmbIMIBfeahzNcQ7OmHHzqvyHyC8rGIkInE0xdFsHcfqjsb6hasedy5VTX3EecWZ3RE7FpNjuV3PLAA==}
'@unocss/preset-attributify@66.3.3':
resolution: {integrity: sha512-22+0Cqqu09q+xHfZ3Wk8Coxe5m6PmpgWz4U5xrEC8056UfG3Q1KEqoCxy2wySJIq8SqxQ30Nlll7oMa31B8Krw==}
'@unocss/preset-icons@66.4.0':
resolution: {integrity: sha512-Fm4/wgNfnVrJgmFrWs9JUjJy+il57hM+4qilSo7zR0QaeyES1z+VnIavGAPI02neBSztIHR8Rh6+6/bhVmByzg==}
'@unocss/preset-icons@66.3.3':
resolution: {integrity: sha512-Bmhiev05BN/horlgnyZ8gzQWZKd7oVpUBWD66X7U/dgkLdO6B5GIIsdO5Fi7JLeMDmyXm6vlYk0YQhiTbx8l9w==}
'@unocss/preset-mini@66.4.0':
resolution: {integrity: sha512-gOdTB9qo5PIusB8WTyCnkwc/GQT7ifAYzn4a+wuk51Ml3i+JxxN90l25dRlgw6hsyx2LgX/CHMzoKXYzuqsnPg==}
'@unocss/preset-mini@66.3.3':
resolution: {integrity: sha512-pz8rgvHRYS/6fsZNtG7iArLzwANnLy5GkHY/lbuqkWhO2S2Nf7kpJCbR/uV/XeuFsLnYcZW3NLOmelfvZvJamA==}
'@unocss/preset-tagify@66.4.0':
resolution: {integrity: sha512-DeIwGoW39iGI4BHz53PWJk2HTOqzJKWQnGBwYb0qw3+PknGRFg18ERRwm4KBGQjyAjt46sIrGm9Zxu5Y9wYh+w==}
'@unocss/preset-tagify@66.3.3':
resolution: {integrity: sha512-L1Ez7Y4uBaW+wiv1BOQygpfhseSt3EZ53jqkl7fxl1EKVsJy6SuZgJxlXEHUYp9xYdSp6EHq2CfL8UevaR+loA==}
'@unocss/preset-typography@66.4.0':
resolution: {integrity: sha512-iWPsCzmUBzwHQRq7cHbtkWAy6V1S4QyzitT6cLf4241njeHnjMJHWwrpyfYNCrdeESjgO9HuoGiyevvqcQ9mRw==}
'@unocss/preset-typography@66.3.3':
resolution: {integrity: sha512-aQXiGCObvWD9grfUpm0d5nzN+Cpvag0rHP39UjUKb0xSTzY09VzwDrua4kWVO5wJLNK6/L70osyhEgmC3qToxA==}
'@unocss/preset-uno@66.4.0':
resolution: {integrity: sha512-1Ep9gkxsW6hfEeZUjJTNofNbZ2/SgFohKb41U9DwBoXCOhGYTE2nmjr6EgoooF6XQNicPNa0tO6xVM/8n9z/NQ==}
'@unocss/preset-uno@66.3.3':
resolution: {integrity: sha512-Tiho4LidpuMHrB19GHTU6XrL0A5eFELHk9ebQ/3WeTy+K/9a6Hn5zsHJe5UCtOsEcUdKB33oZx0hXUp93hb/YQ==}
'@unocss/preset-web-fonts@66.4.0':
resolution: {integrity: sha512-pq9lOuR0VoshLaWlZNqM8A3V9DtsGZEmnX6qAzXCBF7LKO72gFKBn+K2IB6TxET0fMV0pagwhezzU5Jnu9nbMw==}
'@unocss/preset-web-fonts@66.3.3':
resolution: {integrity: sha512-ysKZeC7TXxRiqnNL9GxZFGMKFAHXrcaqozPaEOIJ40dvzbJt8IMLyFndZkcFMcgDCV0pFh/y37mGxxxARO9+pQ==}
'@unocss/preset-wind3@66.4.0':
resolution: {integrity: sha512-9Qo8W3TBcSDtQDV/J1sJrsTa4AHss+wxzZj1ngyHUpgZTE45KEaHH0zEjxM04oC5hrOU9FqRZgwV8Q03UR4v8w==}
'@unocss/preset-wind3@66.3.3':
resolution: {integrity: sha512-iXmjvPqvmPTo4z7epQDqHxzlGRsbLJEgfETqTrRJeagvFG7Gs+ajS8cQhbf6wL01dSRHjvhVXi3MsIvqfHHXOw==}
'@unocss/preset-wind4@66.4.0':
resolution: {integrity: sha512-Ut0B8JRt+aDjHJxZpwm4RtiBBEHE//XBhFFWMz2iljPZLPgN/uhbwr/M53yvpoA07Bz4IhtkaSsgOTLCSEsN0w==}
'@unocss/preset-wind4@66.3.3':
resolution: {integrity: sha512-JSJTXVJel6kX+u4Ktt6JGnukYWYhKxmjgORTwclUpokRHgEoD+xsh0Rz4YGJ1fWSnzNslNQhWP9yDRByVPHWwA==}
'@unocss/preset-wind@66.4.0':
resolution: {integrity: sha512-M1RrLvr827F6jNZsWjvM8FqhJgLR+bJKouhfPhixQFk00dqmS0NiFMKhMEt4kMtByh0fR+CBsEmB0um/vw+T3A==}
'@unocss/preset-wind@66.3.3':
resolution: {integrity: sha512-3Mxl/TDPcv8nNKdFe3WKdlXE6de+lCaaizEH86BILW3ZeyPU9aKzWcZIoxohla0a6zMxDQ2+Gf+7EwaOvpqo7Q==}
'@unocss/reset@66.4.0':
resolution: {integrity: sha512-zbH648K61/Umjy2tCj481ETMuaOlKjyzlXCvVO+U5dF1LhoWM2B7/mdBAiz/cmsKTeE2SfpUmusTRQr6X3n0/Q==}
'@unocss/reset@66.3.3':
resolution: {integrity: sha512-VIeR/mIcCL89/1uA1KM1QCYca4aeIGqEHMTJL1nCD4v+7wk6XhNXhsp5gMIHo+V804SUSmATWaeHTiKpiFu7AQ==}
'@unocss/rule-utils@0.60.4':
resolution: {integrity: sha512-7qUN33NM4T/IwWavm9VIOCZ2+4hLBc0YUGxcMNTDZSFQRQLkWe3N5dOlgwKXtMyMKatZfbIRUKVDUgvEefoCTA==}
'@unocss/rule-utils@66.4.0':
resolution: {integrity: sha512-cWqs6Vre54iwbeYmJIjx1I912M3zNXYQ+lvytkn3NMysNsJlYYhyM4T0L6Jt3dz74X7I4vTcN0sQvVeE2TS3Fg==}
engines: {node: '>=14'}
'@unocss/rule-utils@66.3.3':
resolution: {integrity: sha512-QKgVGV5nRRnK44/reUKFLAc5UGyl98vz3hrfk8JI8pVza58vmQWTdAB2rIpNJ5a5j+EkWfDOUlGQaOrIeYGLdg==}
engines: {node: '>=14'}
'@unocss/transformer-attributify-jsx@66.4.0':
resolution: {integrity: sha512-jDCzDAqGft3WR0cYGJWdghRJnSnu0dqnMNyii0avp/v2qH2J+X6Lmbn6y11sdW9krkPTtXnuF29nd/XWbK7leg==}
'@unocss/transformer-attributify-jsx@66.3.3':
resolution: {integrity: sha512-ENNYFk5wrI4jlxn0tWGeR9QGxflAfZue3X2ABg0KSVOiYyIOsrHqtdoiLYkuCA9idRlBZPQxePJKcPWt1r/tYA==}
'@unocss/transformer-compile-class@66.4.0':
resolution: {integrity: sha512-QETg2SAzmU15e5QmM9lPoWE6Yq8O/pcjLkSrL4HhkARnrEFCiRO3nohXXA/bdnu1bRLxgYp43Q1JwVGPooeb4Q==}
'@unocss/transformer-compile-class@66.3.3':
resolution: {integrity: sha512-VTEFuwp3iajGWyEFwmO5LRvOjgZM1TK+4rX5Q79xyTAPkLAKgOa03Ne8+kU8oG0TQEa4mXVw6ul9McM7UBJh1w==}
'@unocss/transformer-directives@66.4.0':
resolution: {integrity: sha512-QOKQNEEuG/WRdD5thYgMWh/RFQtBpk0T1g5bobWzxi4Z0HxIpUKhu7bgmN9pUzeiN5rW8O42aNHMzIR9thP/1g==}
'@unocss/transformer-directives@66.3.3':
resolution: {integrity: sha512-11T7fmYk/XZcqFDn4qiIvs04mJhUtAoha5Y99bVE+L3byWa6BT4jb5aSAKk+24q5aynwgB++4RgfQxarj69WTw==}
'@unocss/transformer-variant-group@66.4.0':
resolution: {integrity: sha512-6GEtDyVuac06MVeVmAlZHQ4KvWivplHasYWcRll1517XnnCcTJq7qScHv8OoiL6MOYLyTt0hWlecWubESP3MPg==}
'@unocss/transformer-variant-group@66.3.3':
resolution: {integrity: sha512-uhK81pbJfXJFYaXxOoIFVEG8/Kx1iaAkTwRB6c+WNUfl9GiKyYQcrI7bETgCPPbg230Z68jVICBgBATeLJ31vQ==}
'@unocss/vite@66.3.3':
resolution: {integrity: sha512-uu3smeEW6q36ri6vydRx2GiTGF5O/J80Fr4GLmLiwfpt2YnPHraO7XHVR5/mwG2Oz5Kov0uGvxVsdgxZABKRgw==}
'@unocss/vite@66.4.0':
resolution: {integrity: sha512-TCfHwjU6L5ddtTsRe2RmYy6y9zTsu7SD+lFiD5fidUh3FJ80M9wcE3+xNAdjYEdbow4bkF8IzZPbImr2C9imFw==}
peerDependencies:
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0
'@vant/popperjs@1.3.0':
resolution: {integrity: sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw==}
'@vant/use@1.6.0':
resolution: {integrity: sha512-PHHxeAASgiOpSmMjceweIrv2AxDZIkWXyaczksMoWvKV2YAYEhoizRuk/xFnKF+emUIi46TsQ+rvlm/t2BBCfA==}
peerDependencies:
vue: ^3.0.0
'@vitejs/plugin-vue-jsx@5.0.1':
resolution: {integrity: sha512-X7qmQMXbdDh+sfHUttXokPD0cjPkMFoae7SgbkF9vi3idGUKmxLcnU2Ug49FHwiKXebfzQRIm5yK3sfCJzNBbg==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -1724,10 +1693,6 @@ packages:
crypt@0.0.2:
resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
css-tree@2.3.1:
resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
css-tree@3.1.0:
resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
@@ -1743,6 +1708,9 @@ packages:
resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==}
engines: {node: '>=0.12'}
daisyui@5.0.50:
resolution: {integrity: sha512-c1PweK5RI1C76q58FKvbS4jzgyNJSP6CGTQ+KkZYzADdJoERnOxFoeLfDHmQgxLpjEzlYhFMXCeodQNLCC9bow==}
dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
@@ -2279,9 +2247,6 @@ packages:
hosted-git-info@2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
hover.css@2.3.2:
resolution: {integrity: sha512-eXch700vfcoSSAcEcU154lTHBYQy2cjGT+yJZtkKWxASBBiPVkqYOdMAa0sz4cYj2YIrl1FRE2s5O4EgOaIpig==}
husky@8.0.3:
resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==}
engines: {node: '>=14'}
@@ -2489,17 +2454,10 @@ packages:
resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
engines: {node: '>=0.10.0'}
jiti@1.21.7:
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
hasBin: true
jiti@2.4.2:
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
hasBin: true
jquery@3.7.1:
resolution: {integrity: sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -2677,9 +2635,6 @@ packages:
md5@2.3.0:
resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
mdn-data@2.0.30:
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
mdn-data@2.12.2:
resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==}
@@ -3493,9 +3448,6 @@ packages:
resolution: {integrity: sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==}
engines: {node: '>=0.10.0'}
unconfig@0.3.13:
resolution: {integrity: sha512-N9Ph5NC4+sqtcOjPfHrRcHekBCadCXWTBzp2VYYbySOHW0PfD9XLCeXshTXjkPYwLrBr9AtSeU0CZmkYECJhng==}
unconfig@7.3.2:
resolution: {integrity: sha512-nqG5NNL2wFVGZ0NA/aCFw0oJ2pxSf1lwg4Z5ill8wd7K4KX/rQbHlwbh+bjctXL5Ly1xtzHenHGOK0b+lG6JVg==}
@@ -3524,11 +3476,11 @@ packages:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
unocss@66.3.3:
resolution: {integrity: sha512-HSB+K4/EbouwYmxpPU52cg0exua7PUr2IAJZBV3iai6tPdMcJ0c8jXaw7G+2L+ffruVFTcS0e2kE4OrR8BKDLg==}
unocss@66.4.0:
resolution: {integrity: sha512-rT88p+Q0O3BX9WmWE1EQi4eNXdRhrFxQRBSvjGXFuWSMZWGWM66jF68OBNf7C5uWtVlv1fT9oFJCwW8cvaBQaA==}
engines: {node: '>=14'}
peerDependencies:
'@unocss/webpack': 66.3.3
'@unocss/webpack': 66.4.0
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0
peerDependenciesMeta:
'@unocss/webpack':
@@ -3642,11 +3594,6 @@ packages:
resolution: {integrity: sha512-jdBB2FrWvQC/pnPtIqcLsMaQgjhdb6B7tk1MMyTKapox+tQZbdRP4uLxu/JY0t7fbfDCUMnuelzEYv5GsxHhdg==}
engines: {node: '>= 0.10'}
vant@4.9.20:
resolution: {integrity: sha512-QOv8i6/qBXSYO1DsjaxM+U7Hlgc+pIaChF21t/N4zW4pR4DmVNbEri9vchlzWFMz3R7wnCDfV9usOeXCyjHgPQ==}
peerDependencies:
vue: ^3.0.0
vinyl-fs@3.0.3:
resolution: {integrity: sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==}
engines: {node: '>= 0.10'}
@@ -3725,10 +3672,8 @@ packages:
'@vue/composition-api':
optional: true
vue-flow-layout@0.1.1:
resolution: {integrity: sha512-JdgRRUVrN0Y2GosA0M68DEbKlXMqJ7FQgsK8CjQD2vxvNSqAU6PZEpi4cfcTVtfM2GVOMjHo7GKKLbXxOBqDqA==}
peerDependencies:
vue: ^3.4.37
vue-flow-layout@0.2.0:
resolution: {integrity: sha512-zKgsWWkXq0xrus7H4Mc+uFs1ESrmdTXlO0YNbR6wMdPaFvosL3fMB8N7uTV308UhGy9UvTrGhIY7mVz9eN+L0Q==}
vue-i18n@9.14.4:
resolution: {integrity: sha512-B934C8yUyWLT0EMud3DySrwSUJI7ZNiWYsEEz2gknTthqKiG4dzWE/WSa8AzCuSQzwBEv4HtG1jZDhgzPfWSKQ==}
@@ -4493,22 +4438,20 @@ snapshots:
'@types/web-bluetooth@0.0.16': {}
'@unocss/astro@66.3.3(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2))(vue@3.5.17(typescript@5.8.3))':
'@unocss/astro@66.4.0(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2))':
dependencies:
'@unocss/core': 66.3.3
'@unocss/reset': 66.3.3
'@unocss/vite': 66.3.3(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2))(vue@3.5.17(typescript@5.8.3))
'@unocss/core': 66.4.0
'@unocss/reset': 66.4.0
'@unocss/vite': 66.4.0(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2))
optionalDependencies:
vite: 7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2)
transitivePeerDependencies:
- vue
'@unocss/cli@66.3.3':
'@unocss/cli@66.4.0':
dependencies:
'@ampproject/remapping': 2.3.0
'@unocss/config': 66.3.3
'@unocss/core': 66.3.3
'@unocss/preset-uno': 66.3.3
'@unocss/config': 66.4.0
'@unocss/core': 66.4.0
'@unocss/preset-uno': 66.4.0
cac: 6.7.14
chokidar: 3.6.0
colorette: 2.0.20
@@ -4519,159 +4462,131 @@ snapshots:
tinyglobby: 0.2.14
unplugin-utils: 0.2.4
'@unocss/config@0.60.4':
'@unocss/config@66.4.0':
dependencies:
'@unocss/core': 0.60.4
unconfig: 0.3.13
'@unocss/config@66.3.3':
dependencies:
'@unocss/core': 66.3.3
'@unocss/core': 66.4.0
unconfig: 7.3.2
'@unocss/core@0.60.4': {}
'@unocss/core@66.4.0': {}
'@unocss/core@66.3.3': {}
'@unocss/extractor-arbitrary-variants@66.3.3':
'@unocss/extractor-arbitrary-variants@66.4.0':
dependencies:
'@unocss/core': 66.3.3
'@unocss/core': 66.4.0
'@unocss/inspector@66.3.3(vue@3.5.17(typescript@5.8.3))':
'@unocss/inspector@66.4.0':
dependencies:
'@unocss/core': 66.3.3
'@unocss/rule-utils': 66.3.3
'@unocss/core': 66.4.0
'@unocss/rule-utils': 66.4.0
colorette: 2.0.20
gzip-size: 6.0.0
sirv: 3.0.1
vue-flow-layout: 0.1.1(vue@3.5.17(typescript@5.8.3))
transitivePeerDependencies:
- vue
vue-flow-layout: 0.2.0
'@unocss/postcss@0.60.4(postcss@8.5.6)':
'@unocss/postcss@66.4.0(postcss@8.5.6)':
dependencies:
'@unocss/config': 0.60.4
'@unocss/core': 0.60.4
'@unocss/rule-utils': 0.60.4
css-tree: 2.3.1
fast-glob: 3.3.3
magic-string: 0.30.17
postcss: 8.5.6
'@unocss/postcss@66.3.3(postcss@8.5.6)':
dependencies:
'@unocss/config': 66.3.3
'@unocss/core': 66.3.3
'@unocss/rule-utils': 66.3.3
'@unocss/config': 66.4.0
'@unocss/core': 66.4.0
'@unocss/rule-utils': 66.4.0
css-tree: 3.1.0
postcss: 8.5.6
tinyglobby: 0.2.14
'@unocss/preset-attributify@66.3.3':
'@unocss/preset-attributify@66.4.0':
dependencies:
'@unocss/core': 66.3.3
'@unocss/core': 66.4.0
'@unocss/preset-icons@66.3.3':
'@unocss/preset-icons@66.4.0':
dependencies:
'@iconify/utils': 2.3.0
'@unocss/core': 66.3.3
'@unocss/core': 66.4.0
ofetch: 1.4.1
transitivePeerDependencies:
- supports-color
'@unocss/preset-mini@66.3.3':
'@unocss/preset-mini@66.4.0':
dependencies:
'@unocss/core': 66.3.3
'@unocss/extractor-arbitrary-variants': 66.3.3
'@unocss/rule-utils': 66.3.3
'@unocss/core': 66.4.0
'@unocss/extractor-arbitrary-variants': 66.4.0
'@unocss/rule-utils': 66.4.0
'@unocss/preset-tagify@66.3.3':
'@unocss/preset-tagify@66.4.0':
dependencies:
'@unocss/core': 66.3.3
'@unocss/core': 66.4.0
'@unocss/preset-typography@66.3.3':
'@unocss/preset-typography@66.4.0':
dependencies:
'@unocss/core': 66.3.3
'@unocss/preset-mini': 66.3.3
'@unocss/rule-utils': 66.3.3
'@unocss/core': 66.4.0
'@unocss/preset-mini': 66.4.0
'@unocss/rule-utils': 66.4.0
'@unocss/preset-uno@66.3.3':
'@unocss/preset-uno@66.4.0':
dependencies:
'@unocss/core': 66.3.3
'@unocss/preset-wind3': 66.3.3
'@unocss/core': 66.4.0
'@unocss/preset-wind3': 66.4.0
'@unocss/preset-web-fonts@66.3.3':
'@unocss/preset-web-fonts@66.4.0':
dependencies:
'@unocss/core': 66.3.3
'@unocss/core': 66.4.0
ofetch: 1.4.1
'@unocss/preset-wind3@66.3.3':
'@unocss/preset-wind3@66.4.0':
dependencies:
'@unocss/core': 66.3.3
'@unocss/preset-mini': 66.3.3
'@unocss/rule-utils': 66.3.3
'@unocss/core': 66.4.0
'@unocss/preset-mini': 66.4.0
'@unocss/rule-utils': 66.4.0
'@unocss/preset-wind4@66.3.3':
'@unocss/preset-wind4@66.4.0':
dependencies:
'@unocss/core': 66.3.3
'@unocss/extractor-arbitrary-variants': 66.3.3
'@unocss/rule-utils': 66.3.3
'@unocss/core': 66.4.0
'@unocss/extractor-arbitrary-variants': 66.4.0
'@unocss/rule-utils': 66.4.0
'@unocss/preset-wind@66.3.3':
'@unocss/preset-wind@66.4.0':
dependencies:
'@unocss/core': 66.3.3
'@unocss/preset-wind3': 66.3.3
'@unocss/core': 66.4.0
'@unocss/preset-wind3': 66.4.0
'@unocss/reset@66.3.3': {}
'@unocss/reset@66.4.0': {}
'@unocss/rule-utils@0.60.4':
'@unocss/rule-utils@66.4.0':
dependencies:
'@unocss/core': 0.60.4
'@unocss/core': 66.4.0
magic-string: 0.30.17
'@unocss/rule-utils@66.3.3':
'@unocss/transformer-attributify-jsx@66.4.0':
dependencies:
'@unocss/core': 66.3.3
magic-string: 0.30.17
'@babel/parser': 7.28.0
'@babel/traverse': 7.28.0
'@unocss/core': 66.4.0
transitivePeerDependencies:
- supports-color
'@unocss/transformer-attributify-jsx@66.3.3':
'@unocss/transformer-compile-class@66.4.0':
dependencies:
'@unocss/core': 66.3.3
'@unocss/core': 66.4.0
'@unocss/transformer-compile-class@66.3.3':
'@unocss/transformer-directives@66.4.0':
dependencies:
'@unocss/core': 66.3.3
'@unocss/transformer-directives@66.3.3':
dependencies:
'@unocss/core': 66.3.3
'@unocss/rule-utils': 66.3.3
'@unocss/core': 66.4.0
'@unocss/rule-utils': 66.4.0
css-tree: 3.1.0
'@unocss/transformer-variant-group@66.3.3':
'@unocss/transformer-variant-group@66.4.0':
dependencies:
'@unocss/core': 66.3.3
'@unocss/core': 66.4.0
'@unocss/vite@66.3.3(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2))(vue@3.5.17(typescript@5.8.3))':
'@unocss/vite@66.4.0(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2))':
dependencies:
'@ampproject/remapping': 2.3.0
'@unocss/config': 66.3.3
'@unocss/core': 66.3.3
'@unocss/inspector': 66.3.3(vue@3.5.17(typescript@5.8.3))
'@unocss/config': 66.4.0
'@unocss/core': 66.4.0
'@unocss/inspector': 66.4.0
chokidar: 3.6.0
magic-string: 0.30.17
pathe: 2.0.3
tinyglobby: 0.2.14
unplugin-utils: 0.2.4
vite: 7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2)
transitivePeerDependencies:
- vue
'@vant/popperjs@1.3.0': {}
'@vant/use@1.6.0(vue@3.5.17(typescript@5.8.3))':
dependencies:
vue: 3.5.17(typescript@5.8.3)
'@vitejs/plugin-vue-jsx@5.0.1(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2))(vue@3.5.17(typescript@5.8.3))':
dependencies:
@@ -5585,11 +5500,6 @@ snapshots:
crypt@0.0.2: {}
css-tree@2.3.1:
dependencies:
mdn-data: 2.0.30
source-map-js: 1.2.1
css-tree@3.1.0:
dependencies:
mdn-data: 2.12.2
@@ -5616,6 +5526,8 @@ snapshots:
es5-ext: 0.10.64
type: 2.7.3
daisyui@5.0.50: {}
dayjs@1.11.13: {}
de-indent@1.0.2: {}
@@ -6254,8 +6166,6 @@ snapshots:
hosted-git-info@2.8.9: {}
hover.css@2.3.2: {}
husky@8.0.3: {}
iconv-lite@0.4.24:
@@ -6434,12 +6344,8 @@ snapshots:
isobject@3.0.1: {}
jiti@1.21.7: {}
jiti@2.4.2: {}
jquery@3.7.1: {}
js-tokens@4.0.0: {}
js-tokens@9.0.1: {}
@@ -6623,8 +6529,6 @@ snapshots:
crypt: 0.0.2
is-buffer: 1.1.6
mdn-data@2.0.30: {}
mdn-data@2.12.2: {}
memoize-one@6.0.0: {}
@@ -7498,12 +7402,6 @@ snapshots:
unc-path-regex@0.1.2: {}
unconfig@0.3.13:
dependencies:
'@antfu/utils': 0.7.10
defu: 6.1.4
jiti: 1.21.7
unconfig@7.3.2:
dependencies:
'@quansync/fs': 0.1.3
@@ -7562,33 +7460,32 @@ snapshots:
universalify@2.0.1: {}
unocss@66.3.3(postcss@8.5.6)(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2))(vue@3.5.17(typescript@5.8.3)):
unocss@66.4.0(postcss@8.5.6)(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2)):
dependencies:
'@unocss/astro': 66.3.3(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2))(vue@3.5.17(typescript@5.8.3))
'@unocss/cli': 66.3.3
'@unocss/core': 66.3.3
'@unocss/postcss': 66.3.3(postcss@8.5.6)
'@unocss/preset-attributify': 66.3.3
'@unocss/preset-icons': 66.3.3
'@unocss/preset-mini': 66.3.3
'@unocss/preset-tagify': 66.3.3
'@unocss/preset-typography': 66.3.3
'@unocss/preset-uno': 66.3.3
'@unocss/preset-web-fonts': 66.3.3
'@unocss/preset-wind': 66.3.3
'@unocss/preset-wind3': 66.3.3
'@unocss/preset-wind4': 66.3.3
'@unocss/transformer-attributify-jsx': 66.3.3
'@unocss/transformer-compile-class': 66.3.3
'@unocss/transformer-directives': 66.3.3
'@unocss/transformer-variant-group': 66.3.3
'@unocss/vite': 66.3.3(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2))(vue@3.5.17(typescript@5.8.3))
'@unocss/astro': 66.4.0(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2))
'@unocss/cli': 66.4.0
'@unocss/core': 66.4.0
'@unocss/postcss': 66.4.0(postcss@8.5.6)
'@unocss/preset-attributify': 66.4.0
'@unocss/preset-icons': 66.4.0
'@unocss/preset-mini': 66.4.0
'@unocss/preset-tagify': 66.4.0
'@unocss/preset-typography': 66.4.0
'@unocss/preset-uno': 66.4.0
'@unocss/preset-web-fonts': 66.4.0
'@unocss/preset-wind': 66.4.0
'@unocss/preset-wind3': 66.4.0
'@unocss/preset-wind4': 66.4.0
'@unocss/transformer-attributify-jsx': 66.4.0
'@unocss/transformer-compile-class': 66.4.0
'@unocss/transformer-directives': 66.4.0
'@unocss/transformer-variant-group': 66.4.0
'@unocss/vite': 66.4.0(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2))
optionalDependencies:
vite: 7.0.3(@types/node@24.0.11)(jiti@2.4.2)(sass@1.89.2)
transitivePeerDependencies:
- postcss
- supports-color
- vue
unplugin-auto-import@0.16.7(@vueuse/core@9.13.0(vue@3.5.17(typescript@5.8.3)))(rollup@4.44.2):
dependencies:
@@ -7725,13 +7622,6 @@ snapshots:
value-or-function@3.0.0: {}
vant@4.9.20(vue@3.5.17(typescript@5.8.3)):
dependencies:
'@vant/popperjs': 1.3.0
'@vant/use': 1.6.0(vue@3.5.17(typescript@5.8.3))
'@vue/shared': 3.5.17
vue: 3.5.17(typescript@5.8.3)
vinyl-fs@3.0.3:
dependencies:
fs-mkdirp-stream: 1.0.0
@@ -7809,9 +7699,7 @@ snapshots:
dependencies:
vue: 3.5.17(typescript@5.8.3)
vue-flow-layout@0.1.1(vue@3.5.17(typescript@5.8.3)):
dependencies:
vue: 3.5.17(typescript@5.8.3)
vue-flow-layout@0.2.0: {}
vue-i18n@9.14.4(vue@3.5.17(typescript@5.8.3)):
dependencies:

View File

@@ -1,461 +0,0 @@
<script setup lang="js">
import {onMounted} from "vue"
import {createWorker} from "tesseract.js";
import useMobile from "@/hooks/useMobile.ts";
const isMobile = useMobile()
onMounted(async () => {
Array.prototype.clone = function () {
return [].concat(this);
//或者 return this.concat();
}
class Point {
constructor(x, y, time) {
this.x = x;
this.y = y;
this.isControl = false;
this.time = Date.now();
this.lineWidth = 0;
this.isAdd = false;
}
}
class Line {
constructor() {
this.points = new Array();
this.changeWidthCount = 0;
this.lineWidth = 10;
}
}
class HandwritingSelf {
constructor(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext("2d")
let canvasRect = canvas.getBoundingClientRect()
let {width, height} = canvasRect
let dpr = window.devicePixelRatio
if (dpr) {
canvas.style.width = width + "px"
canvas.style.height = height + "px"
canvas.height = height * dpr
canvas.width = width * dpr
this.ctx.scale(dpr, dpr)
}
// this.points = new Array();
this.line = new Line();
this.pointLines = new Array();//Line数组
this.k = 0.5;
this.begin = null;
this.middle = null;
this.end = null;
this.preTime = null;
this.lineWidth = 8;
this.isDown = false;
}
down(x, y) {
// console.log("down:", x, y)
this.isDown = true;
this.line = new Line();
this.line.lineWidth = this.lineWidth;
let currentPoint = new Point(x, y, Date.now());
this.addPoint(currentPoint);
this.preTime = Date.now();
}
move(x, y) {
// console.log("move:",x,y,this.isDown)
if (this.isDown) {
let currentPoint = new Point(x, y, Date.now())
this.addPoint(currentPoint);
this.draw();
}
}
up(x, y) {
// if (e.touches.length > 0) {
let currentPoint = new Point(x, y, Date.now())
this.addPoint(currentPoint);
// }
this.draw(true);
this.pointLines.push(this.line);
this.begin = null;
this.middle = null;
this.end = null;
this.isDown = false;
}
draw(isUp = false) {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.strokeStyle = "rgba(255,20,87,1)";
//绘制不包含this.line的线条
this.pointLines.forEach((line, index) => {
let points = line.points;
this.ctx.beginPath();
this.ctx.ellipse(points[0].x - 1.5, points[0].y, 6, 3, Math.PI / 4, 0, Math.PI * 2);
this.ctx.fill();
this.ctx.beginPath();
this.ctx.moveTo(points[0].x, points[0].y);
let lastW = line.lineWidth;
this.ctx.lineWidth = line.lineWidth;
this.ctx.lineJoin = "round";
this.ctx.lineCap = "round";
let minLineW = line.lineWidth / 4;
let isChangeW = false;
let changeWidthCount = line.changeWidthCount;
for (let i = 1; i <= points.length; i++) {
if (i == points.length) {
this.ctx.stroke();
break;
}
if (i > points.length - changeWidthCount) {
if (!isChangeW) {
this.ctx.stroke();//将之前的线条不变的path绘制完
isChangeW = true;
if (i > 1 && points[i - 1].isControl)
continue;
}
let w = (lastW - minLineW) / changeWidthCount * (points.length - i) + minLineW;
points[i - 1].lineWidth = w;
this.ctx.beginPath();//为了开启新的路径 否则每次stroke 都会把之前的路径在描一遍
// this.ctx.strokeStyle = "rgba("+Math.random()*255+","+Math.random()*255+","+Math.random()*255+",1)";
this.ctx.lineWidth = w;
this.ctx.moveTo(points[i - 1].x, points[i - 1].y);//移动到之前的点
this.ctx.lineTo(points[i].x, points[i].y);
this.ctx.stroke();//将之前的线条不变的path绘制完
} else {
if (points[i].isControl && points[i + 1]) {
this.ctx.quadraticCurveTo(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y);
} else if (i >= 1 && points[i - 1].isControl) {//上一个是控制点 当前点已经被绘制
} else
this.ctx.lineTo(points[i].x, points[i].y);
}
}
})
//绘制this.line线条
let points;
if (isUp)
points = this.line.points;
else
points = this.line.points.clone();
//当前绘制的线条最后几个补点 贝塞尔方式增加点
let count = 0;
let insertCount = 0;
let i = points.length - 1;
let endPoint = points[i];
let controlPoint;
let startPoint;
while (i >= 0) {
if (points[i].isControl == true) {
controlPoint = points[i];
count++;
} else {
startPoint = points[i];
}
if (startPoint && controlPoint && endPoint) {//使用贝塞尔计算补点
let dis = this.z_distance(startPoint, controlPoint) + this.z_distance(controlPoint, endPoint);
let insertPoints = this.BezierCalculate([startPoint, controlPoint, endPoint], Math.floor(dis / 6) + 1);
insertCount += insertPoints.length;
var index = i;//插入位置
// 把insertPoints 变成一个适合splice的数组包含splice前2个参数的数组
insertPoints.unshift(index, 1);
Array.prototype.splice.apply(points, insertPoints);
//补完点后
endPoint = startPoint;
startPoint = null;
}
if (count >= 6)
break;
i--;
}
//确定最后线宽变化的点数
let changeWidthCount = count + insertCount;
if (isUp)
this.line.changeWidthCount = changeWidthCount;
//制造椭圆头
this.ctx.fillStyle = "rgba(255,20,87,1)"
this.ctx.beginPath();
this.ctx.ellipse(points[0].x - 1.5, points[0].y, 6, 3, Math.PI / 4, 0, Math.PI * 2);
this.ctx.fill();
// console.log('points', points)
this.ctx.beginPath();
this.ctx.moveTo(points[0].x, points[0].y);
let lastW = this.line.lineWidth;
this.ctx.lineWidth = this.line.lineWidth;
this.ctx.lineJoin = "round";
this.ctx.lineCap = "round";
let minLineW = this.line.lineWidth / 4;
let isChangeW = false;
for (let i = 1; i <= points.length; i++) {
if (i == points.length) {
this.ctx.stroke();
break;
}
//最后的一些点线宽变细
if (i > points.length - changeWidthCount) {
if (!isChangeW) {
this.ctx.stroke();//将之前的线条不变的path绘制完
isChangeW = true;
if (i > 1 && points[i - 1].isControl)
continue;
}
//计算线宽
let w = (lastW - minLineW) / changeWidthCount * (points.length - i) + minLineW;
points[i - 1].lineWidth = w;
this.ctx.beginPath();//为了开启新的路径 否则每次stroke 都会把之前的路径在描一遍
// this.ctx.strokeStyle = "rgba(" + Math.random() * 255 + "," + Math.random() * 255 + "," + Math.random() * 255 + ",0.5)";
this.ctx.lineWidth = w;
this.ctx.moveTo(points[i - 1].x, points[i - 1].y);//移动到之前的点
this.ctx.lineTo(points[i].x, points[i].y);
this.ctx.stroke();//将之前的线条不变的path绘制完
} else {
if (points[i].isControl && points[i + 1]) {
this.ctx.quadraticCurveTo(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y);
} else if (i >= 1 && points[i - 1].isControl) {//上一个是控制点 当前点已经被绘制
} else
this.ctx.lineTo(points[i].x, points[i].y);
}
}
}
addPoint(p) {
if (this.line.points.length >= 1) {
let last_point = this.line.points[this.line.points.length - 1]
let distance = this.z_distance(p, last_point);
if (distance < 10) {
return;
}
}
if (this.line.points.length === 0) {
this.begin = p;
p.isControl = true;
this.pushPoint(p);
} else {
this.middle = p;
let controlPs = this.computeControlPoints(this.k, this.begin, this.middle, null);
this.pushPoint(controlPs.first);
this.pushPoint(p);
p.isControl = true;
this.begin = this.middle;
}
}
pushPoint(p) {
//排除重复点
if (this.line.points.length >= 1) {
let last = this.line.points[this.line.points.length - 1]
if (last.x === p.x && last.y === p.y) return;
}
this.line.points.push(p);
}
computeControlPoints(k, begin, middle, end) {
if (k > 0.5 || k <= 0)
return;
let diff1 = new Point(middle.x - begin.x, middle.y - begin.y)
let diff2 = null;
if (end)
diff2 = new Point(end.x - middle.x, end.y - middle.y)
// let l1 = (diff1.x ** 2 + diff1.y ** 2) ** (1 / 2)
// let l2 = (diff2.x ** 2 + diff2.y ** 2) ** (1 / 2)
let first = new Point(middle.x - (k * diff1.x), middle.y - (k * diff1.y))
let second = null;
if (diff2)
second = new Point(middle.x + (k * diff2.x), middle.y + (k * diff2.y))
return {first: first, second: second}
}
z_distance(b, e) {
return Math.sqrt(Math.pow(e.x - b.x, 2) + Math.pow(e.y - b.y, 2));
}
BezierCalculate(poss, precision) {
//维度,坐标轴数(二维坐标,三维坐标...
let dimersion = 2;
//贝塞尔曲线控制点数(阶数)
let number = poss.length;
//控制点数不小于 2 ,至少为二维坐标系
if (number < 2 || dimersion < 2)
return null;
let result = new Array();
//计算杨辉三角
let mi = new Array();
mi[0] = mi[1] = 1;
for (let i = 3; i <= number; i++) {
let t = new Array();
for (let j = 0; j < i - 1; j++) {
t[j] = mi[j];
}
mi[0] = mi[i - 1] = 1;
for (let j = 0; j < i - 2; j++) {
mi[j + 1] = t[j] + t[j + 1];
}
}
//计算坐标点
for (let i = 0; i < precision; i++) {
let t = i / precision;
let p = new Point(0, 0);
p.isAdd = true;
result.push(p);
for (let j = 0; j < dimersion; j++) {
let temp = 0.0;
for (let k = 0; k < number; k++) {
temp += Math.pow(1 - t, number - k - 1) * (j == 0 ? poss[k].x : poss[k].y) * Math.pow(t, k) * mi[k];
}
j == 0 ? p.x = temp : p.y = temp;
}
}
return result;
}
clear() {
this.line = new Line();
this.pointLines = new Array();//Line数组
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
}
const worker = await createWorker({
// logger: m => console.log(m)
});
await worker.loadLanguage('eng');
await worker.initialize('eng');
await worker.setParameters({
tessedit_char_whitelist: 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM'
});
// alert('好了')
let lastTime = Date.now()
let timer = -1
let checkTime = 400
let eventMap = {
down: '',
move: '',
up: '',
}
if (isMobile) {
eventMap = {
down: 'touchstart',
move: 'touchmove',
up: 'touchend',
}
} else {
eventMap = {
down: 'mousedown',
move: 'mousemove',
up: 'mouseup',
}
}
let handwriting = new HandwritingSelf(document.getElementById("canvasId"))
window.addEventListener(eventMap.down, (e) => {
if (Date.now() - lastTime > checkTime) {
handwriting.clear()
} else {
clearTimeout(timer)
}
if (e.type === "touchstart")
handwriting.down(e.touches[0].pageX, e.touches[0].pageY);
else
handwriting.down(e.x, e.y);
})
window.addEventListener(eventMap.move, (e) => {
if (e.type === "touchmove")
handwriting.move(e.touches[0].pageX, e.touches[0].pageY);
else
handwriting.move(e.x, e.y);
})
window.addEventListener(eventMap.up, (e) => {
if (e.type === "touchend")
handwriting.up(e.touches[0].pageX, e.touches[0].pageY);
else
handwriting.up(e.x, e.y);
clearTimeout(timer)
timer = setTimeout(() => {
// console.log('识别');
// handwriting.canvas.toDataURL()
// var MIME_TYPE = "image/png";
// var imgURL = handwriting.canvas.toDataURL(MIME_TYPE);
// var dlLink = document.createElement('a');
// dlLink.download = 'fileName.png';
// dlLink.href = imgURL;
// dlLink.dataset.downloadurl = [MIME_TYPE, dlLink.download, dlLink.href].join(':');
//
// document.body.appendChild(dlLink);
// dlLink.click();
// document.body.removeChild(dlLink);
(async () => {
const {data: {text}} = await worker.recognize(handwriting.canvas);
console.log(text);
if (isMobile){
alert(text)
}
})();
}, checkTime)
lastTime = Date.now()
})
})
</script>
<template>
<div class="mobile">
<canvas id="canvasId"></canvas>
</div>
</template>
<style>
html, body {
overflow: hidden;
}
</style>
<style scoped lang="scss">
.mobile {
width: 100vw;
height: 100vh;
background: var(--color-background);
overflow: hidden;
canvas {
width: 100%;
height: 100%;
//border: 1px solid gray;
}
}
</style>

View File

@@ -1,5 +1,4 @@
//@import '/node_modules/element-plus/dist/index.css';
//@use "/node_modules/hover.css" as *;
@use "anim" as *;
@use 'element-plus/theme-chalk/dark/css-vars' as *;
@@ -7,30 +6,18 @@
//修改element-ui的进度条底色
--el-border-color-lighter: #d1d1d1 !important;
--color-background: #E6E8EB;
//--color-main-bg: #E6E8EB;
--color-main-bg: rgb(238, 240, 244);
--color-second-bg: rgb(247, 247, 247);
--color-third-bg: rgb(213, 215, 217);
--color-item-bg: rgb(228, 230, 232);
--color-item-hover: white;
//--color-item-active: rgb(75, 110, 175);
--color-item-active: rgb(253, 246, 236);
--color-item-border: rgb(226, 226, 226);
--color-header-bg: white;
--color-tooltip-bg: white;
--color-tooltip-shadow: #d9d9d9;
--color-font-1: rgb(91, 91, 91);
--color-font-2: rgb(46, 46, 46);
--color-font-3: rgb(75, 85, 99);
--color-font-active-1: white;
--color-font-active-2: whitesmoke;
--color-main-active: rgb(12, 140, 233);
--color-primary: rgb(12, 140, 233);
--color-scrollbar: rgb(147, 173, 227);
--color-gray: gray;
--color-sub-gray: #c0bfbf;
--practice-wrapper-translateX: 1px;
@@ -39,11 +26,10 @@
--toolbar-height: 3.2rem;
--panel-width: 24rem;
--space: 1rem;
--radius: .5rem;
--shadow: rgba(0, 0, 0, 0.08) 0px 4px 12px;
--panel-margin-left: calc(50% + var(--toolbar-width) / 2 + 1rem);
--article-panel-margin-left: calc(50% + var(--article-width) / 2 + 3.5rem);
--anim-time: 0.5s;
--anim-time: 0.3s;
--color-input-bg: white;
--color-input-border: #bfbfbf;
@@ -62,29 +48,47 @@
--btn-primary: rgb(75, 85, 99);
--btn-info: #909399;
--color-primary: #E6E8EB;
--color-second: rgb(247, 247, 247);
--color-third: rgb(226 232 240 / 1);
--color-card-active: #FED7AA;
--color-list-item-active: rgb(253, 246, 236);
--color-icon-hightlight: #E6A23C;
//--color-icon-hightlight: rgb(12, 140, 233);
--color-sub-text: gray;
--color-main-text: rgb(91, 91, 91);
--color-select-bg: rgb(12, 140, 233);
--color-select-text: white;
--color-notice-bg: rgb(247, 247, 247);
}
html.dark {
--color-main-bg: rgba(14, 18, 23, 1);
--color-second-bg: rgb(30, 31, 34);
--color-third-bg: rgb(43, 45, 48);
--color-primary: #0E1217;
--color-second: rgb(30, 31, 34);
--color-third: rgb(43, 45, 48);
--color-card-active: rgb(84, 84, 84);
--color-list-item-active: rgb(84, 84, 84);
--color-icon-hightlight: #E6A23C;
--color-sub-text: #b8b8b8;
--color-main-text: rgba(249, 250, 251, 0.8);
--color-select-bg: rgb(147, 173, 227);
--color-select-text: black;
--color-notice-bg: rgb(43, 45, 48);
--color-item-bg: rgb(43, 45, 48);
--color-item-hover: rgb(67, 69, 74);
--color-item-active: rgb(84, 84, 84);
--color-item-border: rgb(41, 41, 41);
--color-header-bg: rgb(51, 51, 51);
--color-tooltip-bg: #252525;
--color-tooltip-shadow: #3b3b3b;
--color-font-1: rgba(249, 250, 251, 0.8);
--color-font-2: rgba(255, 255, 255, 0.5);
--color-font-3: rgba(255, 255, 255, 0.3);
--color-gray: #bebebe;
--color-sub-gray: #383737;
--color-main-active: rgb(147, 173, 227);
--color-scrollbar: rgb(92, 93, 94);
--color-input-bg: rgba(14, 18, 23, 1);
@@ -164,7 +168,7 @@ html, body {
padding: 0;
margin: 0;
overflow-x: hidden;
color: var(--color-font-1);
color: var(--color-main-text);
font-family: var(--font-family);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -226,7 +230,7 @@ a {
background: var(--color-textarea-bg);
&:focus {
border: 1px solid var(--color-main-active);
border: 1px solid var(--color-select-bg);
}
&[readonly] {
@@ -340,7 +344,7 @@ footer {
width: 100%;
box-sizing: border-box;
background: var(--color-item-bg);
color: var(--color-font-1);
color: var(--color-main-text);
font-size: 1.1rem;
border-radius: .5rem;
display: flex;
@@ -373,32 +377,32 @@ footer {
opacity: 0;
}
&.active {
background: var(--color-list-item-active);
.item-sub-title {
color: var(--color-sub-text);
}
.volume, .collect, .easy, .fill {
color: var(--color-icon-hightlight);
}
}
&:hover {
background: var(--color-item-hover);
@extend .active;
.volume, .collect, .easy {
opacity: 1;
}
}
&.active {
background: var(--color-item-active);
$c: #E6A23C;
.phonetic, .item-sub-title {
//color: var(--color-gray) !important;
}
.volume, .collect, .easy, .fill {
color: $c;
}
}
.item-title {
display: flex;
align-items: center;
gap: .5rem;
color: var(--color-font-1);
color: var(--color-main-text);
.word {
font-size: 1.2rem;
@@ -415,7 +419,6 @@ footer {
font-size: 1rem;
color: gray;
}
}
.word-shadow {
@@ -427,7 +430,7 @@ footer {
.common-title {
min-height: 2.8rem;
font-size: 1.1rem;
color: var(--color-font-1);
color: var(--color-main-text);
display: flex;
justify-content: center;
align-items: center;
@@ -463,7 +466,8 @@ footer {
}
.card {
@apply rounded-xl bg-white p-4 mb-5 box-border relative;
@apply rounded-xl p-4 mb-5 box-border relative;
background: var(--color-second);
}
.center {
@@ -476,7 +480,7 @@ footer {
.book {
@extend .anim;
@apply p-4 rounded-md bg-slate-200 relative cursor-pointer h-40 hover:bg-orange-200 flex flex-col justify-between;
@apply p-4 rounded-md relative cursor-pointer h-40 bg-third hover:bg-card-active flex flex-col justify-between;
}
.line {

View File

@@ -59,7 +59,7 @@ defineEmits(['click'])
transition: all .3s;
//background: #999;
//background: rgb(60, 63, 65);
//background: var(--color-second-bg);
//background: var(--color-second);
height: 2.5rem;
line-height: 1;
position: relative;

View File

@@ -41,7 +41,6 @@ $w: 1.4rem;
border-radius: .3rem;
background: transparent;
transition: all .3s;
//color: var(--color-main-active);
&:hover:not(.disabled) {
background: var(--color-primary);
@@ -58,4 +57,4 @@ $w: 1.4rem;
height: $w;
}
}
</style>
</style>

View File

@@ -1,14 +1,13 @@
import {createApp} from 'vue'
import './assets/css/style.scss'
import 'virtual:uno.css';
import App from './App.vue'
// import Mobile from './Mobile.vue'
import {createPinia} from "pinia"
import ZH from "@/locales/zh-CN.ts";
import {createI18n} from 'vue-i18n'
import router from "@/router.ts";
import VueVirtualScroller from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import 'virtual:uno.css';
import './global.d.ts'
const i18n = createI18n({
@@ -20,7 +19,6 @@ const i18n = createI18n({
})
const pinia = createPinia()
// const app = createApp(Mobile)
const app = createApp(App)
app.use(VueVirtualScroller)

View File

@@ -453,17 +453,17 @@ function importData(e) {
</div>
</div>
</div>
<div v-if="tabIndex === 5" class="about">
<div v-if="tabIndex === 5" class="center flex-col">
<h1>Type Words</h1>
<p>
本项目完全开源好用请大家多多点Star
<p class="w-100 text-xl">
感谢使用本项目本项目是开源项目如果觉得有帮助请在 GitHub 点个 Star您的支持是我持续改进的动力
</p>
<p>
GitHub地址<a href="https://github.com/zyronon/typing-word">https://github.com/zyronon/typing-word</a>
GitHub地址<a href="https://github.com/zyronon/TypeWords" target="_blank">https://github.com/zyronon/TypeWords</a>
</p>
<p>
反馈<a
href="https://github.com/zyronon/typing-word/issues">https://github.com/zyronon/typing-word/issues</a>
href="https://github.com/zyronon/TypeWords/issues" target="_blank">https://github.com/zyronon/TypeWords/issues</a>
</p>
<div class="text-md color-gray">
Build {{ gitLastCommitHash }}
@@ -503,8 +503,8 @@ function importData(e) {
gap: .6rem;
&.active {
background: var(--color-main-active);
color: var(--color-input-bg);
background: var(--color-select-bg);
color: var(--color-select-text);
}
}
}
@@ -551,7 +551,7 @@ function importData(e) {
border: 1px solid gray;
border-radius: .2rem;
padding: 0 .3rem;
background: var(--color-second-bg);
background: var(--color-second);
color: var(--color-font-1);
}
}

View File

@@ -119,7 +119,7 @@ function startStudy() {
</div>
<Dialog v-model="showAddChooseDialog" title="选项">
<div class="color-black px-6 w-100">
<div class="color-main px-6 w-100">
<div class="cursor-pointer hover:bg-black/10 p-2 rounded"
@click="showAddChooseDialog = false,showSearchDialog = true">选择一本书籍
</div>
@@ -131,7 +131,7 @@ function startStudy() {
:show-close="false"
@close="searchKey = ''"
:header="false">
<div class="color-black w-140">
<div class="color-main w-140">
<div class="p-4">
<Input v-if="showSearchDialog" :autofocus="true" v-model="searchKey"/>
</div>
@@ -146,7 +146,7 @@ function startStudy() {
</div>
</div>
</div>
<div v-else class="h-40 center flex-col text-xl color-black/60">
<div v-else class="h-40 center flex-col text-xl color-main">
<div> 请输入书籍名称搜索</div>
<div>或直接在书籍列表选中</div>
</div>

View File

@@ -227,7 +227,7 @@ useWindowClick(() => showExport = false)
height: 100vh;
box-sizing: border-box;
color: var(--color-font-1);
background: var(--color-second-bg);
background: var(--color-second);
display: flex;
.close {

View File

@@ -1,661 +0,0 @@
<script setup lang="ts">
import {Article, DefaultArticle, Sentence, TranslateEngine} from "@/types.ts";
import BaseButton from "@/components/BaseButton.vue";
import EditAbleText from "@/pages/pc/components/EditAbleText.vue";
import {Icon} from "@iconify/vue";
import {
getNetworkTranslate,
getSentenceAllText,
getSentenceAllTranslateText,
renewSectionTexts,
renewSectionTranslates
} from "@/hooks/translate.ts";
import {MessageBox} from "@/utils/MessageBox.tsx";
import {getSplitTranslateText, usePlaySentenceAudio} from "@/hooks/article.ts";
import {cloneDeep, last} from "lodash-es";
import {watch} from "vue";
import Empty from "@/components/Empty.vue";
import {UploadProps} from "element-plus";
import {_nextTick, _parseLRC} from "@/utils";
import * as Comparison from "string-comparison"
import audio from '/public/sound/article/nce2-1/1.mp3'
import BaseIcon from "@/components/BaseIcon.vue";
import Dialog from "@/pages/pc/components/dialog/Dialog.vue";
interface IProps {
article?: Article,
type?: 'single' | 'batch'
}
const props = withDefaults(defineProps<IProps>(), {
article: () => getDefaultArticle(),
type: 'single'
})
const emit = defineEmits<{
save: [val: Article],
saveAndNext: [val: Article]
}>()
let networkTranslateEngine = $ref('baidu')
let progress = $ref(0)
let failCount = $ref(0)
let textareaRef = $ref<HTMLTextAreaElement>()
const TranslateEngineOptions = [
{value: 'baidu', label: '百度'},
{value: 'youdao', label: '有道'},
]
let editArticle = $ref<Article>(getDefaultArticle())
watch(() => props.article, val => {
editArticle = cloneDeep(val)
progress = 0
failCount = 0
// let r = getSplitTranslateText(editArticle.textTranslate)
// if (r) {
// editArticle.textTranslate = r
// ElMessage({
// message: '检测到本地翻译未格式化,已自动格式化',
// type: 'success',
// duration: 3000
// })
// }
renewSections()
console.log('ar', editArticle)
}, {immediate: true})
watch(() => editArticle.text, (s) => {
if (!s.trim()) {
editArticle.sections = []
}
})
function renewSections() {
if (editArticle.text.trim()) {
renewSectionTexts(editArticle)
failCount = renewSectionTranslates(editArticle, editArticle.textTranslate)
} else {
editArticle.sections = []
}
}
function appendTranslate(str: string) {
let selectionStart = textareaRef.selectionStart;
let selectionEnd = textareaRef.selectionEnd;
editArticle.textTranslate = editArticle.textTranslate.slice(0, selectionStart) + str + editArticle.textTranslate.slice(selectionEnd)
}
function splitTranslateText() {
if (editArticle.textTranslate.trim()){
editArticle.textTranslate = getSplitTranslateText(editArticle.textTranslate.trim())
renewSections()
}
}
function onPaste(event: ClipboardEvent) {
event.preventDefault()
// @ts-ignore
let paste = (event.clipboardData || window.clipboardData).getData("text");
return MessageBox.confirm(
'是否需要自动分句',
'提示',
() => {
let r = getSplitTranslateText(paste)
if (r) {
appendTranslate(r)
renewSections()
}
},
() => {
appendTranslate(paste)
renewSections()
}, null,
{
confirmButtonText: '需要',
cancelButtonText: '关闭',
}
)
}
function onBlur() {
document.removeEventListener('paste', onPaste);
}
function onFocus() {
document.addEventListener('paste', onPaste);
}
//TODO
async function startNetworkTranslate() {
if (!editArticle.title.trim()) {
return ElMessage.error('请填写标题!')
}
if (!editArticle.text.trim()) {
return ElMessage.error('请填写正文!')
}
renewSectionTexts(editArticle)
//注意!!!
//这里需要用异步因为watch了article.networkTranslate改变networkTranslate了之后会重新设置article.sections
//导致getNetworkTranslate里面拿到的article.sections是废弃的值
setTimeout(async () => {
await getNetworkTranslate(editArticle, TranslateEngine.Baidu, true, (v: number) => {
progress = v
})
failCount = 0
})
}
function saveSentenceTranslate(sentence: Sentence, val: string) {
sentence.translate = val
editArticle.textTranslate = getSentenceAllTranslateText(editArticle)
renewSections()
}
function saveSentenceText(sentence: Sentence, val: string) {
sentence.text = val
editArticle.text = getSentenceAllText(editArticle)
renewSections()
}
function save(option: 'save' | 'saveAndNext') {
// return console.log(cloneDeep(editArticle))
return new Promise((resolve: Function) => {
// console.log('article', article)
// copy(JSON.stringify(article))
editArticle.title = editArticle.title.trim()
editArticle.titleTranslate = editArticle.titleTranslate.trim()
editArticle.text = editArticle.text.trim()
editArticle.textTranslate = editArticle.textTranslate.trim()
if (!editArticle.title) {
ElMessage.error('请填写标题!')
return resolve(false)
}
if (!editArticle.text) {
ElMessage.error('请填写正文!')
return resolve(false)
}
const saveTemp = () => {
emit(option as any, editArticle)
return resolve(true)
}
saveTemp()
})
}
//不知道为什么直接用editArticle取到是空的默认值
defineExpose({save, getEditArticle: () => cloneDeep(editArticle)})
const handleChange: UploadProps['onChange'] = (uploadFile, uploadFiles) => {
console.log(uploadFile)
let reader = new FileReader();
reader.readAsText(uploadFile.raw, 'UTF-8');
reader.onload = function (e) {
let lrc: string = e.target.result as string;
console.log(lrc)
if (lrc.trim()) {
let lrcList = _parseLRC(lrc)
console.log('lrcList', lrcList)
if (lrcList.length) {
editArticle.lrcPosition = editArticle.sections.map((v, i) => {
return v.map((w, j) => {
for (let k = 0; k < lrcList.length; k++) {
let s = lrcList[k]
let d = Comparison.default.cosine.similarity(w.text, s.text)
d = Comparison.default.levenshtein.similarity(w.text, s.text)
d = Comparison.default.longestCommonSubsequence.similarity(w.text, s.text)
// d = Comparison.default.metricLcs.similarity(w.text, s.text)
// console.log(w.text, s.text, d)
if (d >= 0.8) {
w.audioPosition = [s.start, s.end ?? -1]
break
}
}
return w.audioPosition ?? []
})
}).flat()
}
}
}
}
let currentSentence = $ref<Sentence>({} as any)
let editSentence = $ref<Sentence>({} as any)
let preSentence = $ref<Sentence>({} as any)
let showEditAudioDialog = $ref(false)
let sentenceAudioRef = $ref<HTMLAudioElement>()
let audioRef = $ref<HTMLAudioElement>()
function handleShowEditAudioDialog(val: Sentence, i: number, j: number) {
showEditAudioDialog = true
currentSentence = val
editSentence = cloneDeep(val)
preSentence = null
audioRef.pause()
if (j == 0) {
if (i != 0) {
preSentence = last(editArticle.sections[i - 1])
}
} else {
preSentence = editArticle.sections[i][j - 1]
}
if (!editSentence.audioPosition?.length) {
editSentence.audioPosition = [0, 0]
if (preSentence) {
editSentence.audioPosition = [preSentence.audioPosition[1] ?? 0, 0]
}
}
_nextTick(() => {
sentenceAudioRef.currentTime = editSentence.audioPosition[0]
})
}
function recordStart() {
if (sentenceAudioRef.paused) {
sentenceAudioRef.play()
}
editSentence.audioPosition[0] = Number(sentenceAudioRef.currentTime.toFixed(2))
}
function recordEnd() {
if (!sentenceAudioRef.paused) {
sentenceAudioRef.pause()
}
editSentence.audioPosition[1] = Number(sentenceAudioRef.currentTime.toFixed(2))
}
const {playSentenceAudio} = usePlaySentenceAudio()
function saveLrcPosition() {
// showEditAudioDialog = false
currentSentence.audioPosition = cloneDeep(editSentence.audioPosition)
editArticle.lrcPosition = editArticle.sections.map((v, i) => v.map((w, j) => (w.audioPosition ?? []))).flat()
}
function jumpAudio(time: number) {
sentenceAudioRef.currentTime = time
}
function setPreEndTimeToCurrentStartTime() {
if (preSentence) {
editSentence.audioPosition[0] = preSentence.audioPosition[1]
}
}
function setStartTime(val: Sentence, i: number, j: number) {
let preSentence = null
if (j == 0) {
if (i != 0) {
preSentence = last(editArticle.sections[i - 1])
}
} else {
preSentence = editArticle.sections[i][j - 1]
}
if (preSentence) {
val.audioPosition[0] = preSentence.audioPosition[1]
} else {
val.audioPosition[0] = Number(Number(audioRef.currentTime).toFixed(2))
}
}
</script>
<template>
<div class="content">
<div class="row flex flex-col gap-2">
<div class="title">原文</div>
<div class="">标题:</div>
<input
v-model="editArticle.title"
type="text"
class="base-input"
placeholder="请填写原文标题"
/>
<div class="">正文:</div>
<textarea
v-model="editArticle.text"
:readonly="![100,0].includes(progress)"
type="textarea"
class="base-textarea"
placeholder="请复制原文"
>
</textarea>
<div class="justify-between items-center gap-2 flex">
<ol class="py-0 pl-5 my-0 text-base color-black/60">
<li>复制原文</li>
<li>点击 <span class="color-red font-bold">分句</span> 按钮进行自动分句</li>
<li><span class="color-red font-bold">或</span> 手动调整分句,一行一句,段落之间空一行</li>
<li>修改完成后点击 <span class="color-red font-bold">应用</span> 按钮同步到左侧结果栏
</li>
</ol>
<el-button type="primary" @click="renewSections">应用</el-button>
</div>
</div>
<div class="row flex flex-col gap-2">
<div class="title">译文</div>
<div class="flex gap-2">
标题
</div>
<input
v-model="editArticle.titleTranslate"
type="text"
class="base-input"
placeholder="请填写翻译标题"
/>
<div class="flex">
<span>正文:</span>
</div>
<textarea
v-model="editArticle.textTranslate"
:readonly="![100,0].includes(progress)"
@blur="onBlur"
@focus="onFocus"
type="textarea"
class="base-textarea"
placeholder="请填写翻译"
ref="textareaRef"
>
</textarea>
<div class="justify-between items-center gap-2 flex">
<ol class="py-0 pl-5 my-0 text-base color-black/60">
<li>复制译文,如果没有请点击 <span class="color-red font-bold">翻译</span> 按钮</li>
<li>点击 <span class="color-red font-bold">分句</span> 按钮进行自动分句</li>
<li><span class="color-red font-bold">或</span> 手动调整分句,一行一句,段落之间空一行</li>
<li>修改完成后点击 <span class="color-red font-bold">应用</span> 按钮同步到左侧结果栏
</li>
</ol>
<div class="flex flex-col gap-2 items-end">
<div class="translate-item">
{{ progress }}%
<el-select v-model="networkTranslateEngine"
class="w-20"
>
<el-option
v-for="item in TranslateEngineOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<el-button
type="primary"
@click="startNetworkTranslate"
:loading="progress!==0 && progress !== 100"
>翻译
</el-button>
<div>
<el-button type="primary" @click="splitTranslateText">分句</el-button>
<el-button type="primary" @click="renewSections">应用</el-button>
</div>
</div>
</div>
</div>
<div class="row flex flex-col gap-2">
<div class="title">结果</div>
<div class="center">正文、译文与结果均可编辑,修改一处,另外两处会自动同步变动</div>
<div class="flex gap-2">
<BaseButton>添加音频</BaseButton>
<el-upload
class="upload-demo"
:limit="1"
:on-change="handleChange"
:auto-upload="false"
>
<el-button type="primary">添加音频LRC文件</el-button>
</el-upload>
<audio ref="audioRef" :src="editArticle.audioSrc" controls></audio>
</div>
<template v-if="editArticle.sections.length">
<div class="flex-1 overflow-auto flex flex-col">
<div class="flex justify-between bg-black/10 py-2">
<div class="center flex-[7]">内容</div>
<div>|</div>
<div class="center flex-[3]">音频</div>
</div>
<div class="article-translate">
<div class="section " v-for="(item,indexI) in editArticle.sections">
<div class="section-title">第{{ indexI + 1 }}段</div>
<div class="sentence" v-for="(sentence,indexJ) in item">
<div class="flex-[7]">
<EditAbleText
:value="sentence.text"
@save="(e:string) => saveSentenceText(sentence,e)"
/>
<EditAbleText
class="text-lg!"
v-if="sentence.translate"
:value="sentence.translate"
@save="(e:string) => saveSentenceTranslate(sentence,e)"
/>
</div>
<div class="flex-[2] flex justify-end gap-1 items-center">
<div class="flex justify-end gap-2">
<div class="flex flex-col items-center justify-center">
<div>{{ sentence.audioPosition?.[0] ?? 0 }}s</div>
<BaseIcon
@click="setStartTime(sentence,indexI,indexJ)"
:icon="indexI === 0 && indexJ === 0 ?'ic:sharp-my-location':'twemoji:end-arrow'"
:title="indexI === 0 && indexJ === 0 ?'设置开始时间':'使用前一句的结束时间'"
/>
</div>
<div>-</div>
<div class="flex flex-col items-center justify-center">
<div v-if="sentence.audioPosition?.[1] !== -1">{{ sentence.audioPosition?.[1] ?? 0 }}s</div>
<div v-else> 结束</div>
<BaseIcon
@click="sentence.audioPosition[1] = Number(Number(audioRef.currentTime).toFixed(2))"
title="设置结束时间"
icon="ic:sharp-my-location"
/>
</div>
</div>
<div class="flex flex-col">
<BaseIcon :icon="sentence.audioPosition?.length ? 'basil:edit-outline' : 'basil:add-outline'"
@click="handleShowEditAudioDialog(sentence,indexI,indexJ)"/>
<BaseIcon v-if="sentence.audioPosition?.length" icon="hugeicons:play"
@click="playSentenceAudio(sentence,audioRef,editArticle)"/>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="options" v-if="editArticle.text.trim()">
<div class="status">
<span>状态:</span>
<div class="warning" v-if="failCount">
<Icon icon="typcn:warning-outline"/>
共有{{ failCount }}句没有翻译!
</div>
<div class="success" v-else>
<Icon icon="mdi:success-circle-outline"/>
翻译完成!
</div>
</div>
<div class="left">
<BaseButton @click="save('save')">保存</BaseButton>
<BaseButton v-if="type === 'batch'" @click="save('saveAndNext')">保存并添加下一篇</BaseButton>
</div>
</div>
</template>
<Empty v-else text="没有译文对照~"/>
</div>
<Dialog title="设置音频与句子的对应位置(LRC)"
v-model="showEditAudioDialog"
:footer="true"
@close="showEditAudioDialog = false"
@ok="saveLrcPosition"
>
<div class="p-4 pt-0 color-black w-150 flex flex-col gap-2">
<div class="">
教程:点击音频播放按钮,当播放到句子开始时,点击开始时间的 <span class="color-red">记录</span>
按钮;当播放到句子结束时,点击结束时间的 <span class="color-red">记录</span> 按钮,最后再试听是否正确
</div>
<audio ref="sentenceAudioRef" :src="editArticle.audioSrc" controls class="w-full"></audio>
<div class="flex items-center gap-2 space-between mb-2" v-if="editSentence.audioPosition?.length">
<div>{{ editSentence.text }}</div>
<div class="flex items-center gap-2 shrink-0">
<div>
<span>{{ editSentence.audioPosition?.[0] }}s</span>
<span v-if="editSentence.audioPosition?.[1] !== -1"> - {{ editSentence.audioPosition?.[1] }}s</span>
<span v-else> - 结束</span>
</div>
<BaseIcon icon="hugeicons:play"
title="试听"
@click="playSentenceAudio(editSentence,sentenceAudioRef,editArticle)"/>
</div>
</div>
<div class="flex flex-col gap-2">
<div class="flex gap-2 items-center">
<div>开始时间:</div>
<div class="flex space-between flex-1">
<div class="flex items-center gap-2">
<el-input-number v-model="editSentence.audioPosition[0]" :precision="2" :step="0.1">
<template #suffix>
<span>s</span>
</template>
</el-input-number>
<BaseIcon
@click="jumpAudio(editSentence.audioPosition[0])"
title="跳转"
icon="ic:sharp-my-location"
/>
<BaseIcon
@click="setPreEndTimeToCurrentStartTime"
title="使用前一句的结束时间"
icon="twemoji:end-arrow"
/>
</div>
<BaseButton @click="recordStart">记录</BaseButton>
</div>
</div>
<div class="flex gap-2 items-center">
<div>结束时间:</div>
<div class="flex space-between flex-1">
<div class="flex items-center gap-2">
<el-input-number v-model="editSentence.audioPosition[1]" :precision="2" :step="0.1">
<template #suffix>
<span>s</span>
</template>
</el-input-number>
<span>或</span>
<BaseButton size="small" @click="editSentence.audioPosition[1] = -1">结束</BaseButton>
</div>
<BaseButton @click="recordEnd">记录</BaseButton>
</div>
</div>
</div>
</div>
</Dialog>
</div>
</template>
<style scoped lang="scss">
.content {
color: var(--color-article);
flex: 1;
display: flex;
gap: var(--space);
padding: var(--space);
padding-top: .6rem;
}
.row {
flex: 7;
width: 33%;
//height: 100%;
display: flex;
flex-direction: column;
//opacity: 0;
&:nth-child(3) {
flex: 10;
}
.title {
font-weight: bold;
font-size: 1.4rem;
text-align: center;
}
.translate-item {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
gap: calc(var(--space) / 2);
}
.article-translate {
flex: 1;
overflow-y: overlay;
.section {
background: var(--color-textarea-bg);
margin-bottom: 1.2rem;
.section-title {
padding: 0.5rem;
border-bottom: 1px solid var(--color-item-border);
}
&:last-child {
margin-bottom: 0;
}
.sentence {
display: flex;
padding: 0.5rem 1.5rem;
line-height: 1.2;
border-bottom: 1px solid var(--color-item-border);
&:last-child {
border-bottom: none;
}
}
}
}
.options {
display: flex;
align-items: center;
justify-content: space-between;
.status {
display: flex;
align-items: center;
}
.warning {
display: flex;
align-items: center;
font-size: 1.2rem;
color: red;
}
.success {
display: flex;
align-items: center;
font-size: 1.2rem;
color: #67C23A;
}
.left {
gap: var(--space);
display: flex;
}
}
}
</style>

View File

@@ -306,7 +306,7 @@ function setStartTime(val: Sentence, i: number, j: number) {
</template>
</el-popover>
<el-button type="primary" @click="splitText">分句</el-button>
<el-button type="primary" @click="apply">应用</el-button>
<el-button type="primary" @click="() => apply()">应用</el-button>
</div>
</div>
<div class="row flex flex-col gap-2">
@@ -474,7 +474,7 @@ function setStartTime(val: Sentence, i: number, j: number) {
@close="showEditAudioDialog = false"
@ok="saveLrcPosition"
>
<div class="p-4 pt-0 color-black w-150 flex flex-col gap-2">
<div class="p-4 pt-0 color-main w-150 flex flex-col gap-2">
<div class="">
教程点击音频播放按钮当播放到句子开始时点击开始时间的 <span class="color-red">记录</span>
按钮当播放到句子结束时点击结束时间的 <span class="color-red">记录</span> 按钮最后再试听是否正确
@@ -539,8 +539,6 @@ function setStartTime(val: Sentence, i: number, j: number) {
</template>
<style scoped lang="scss">
.content {
color: var(--color-article);
height: 100%;

View File

@@ -234,7 +234,7 @@ useWindowClick(() => showExport = false)
height: 100%;
box-sizing: border-box;
color: var(--color-font-1);
background: var(--color-second-bg);
background: var(--color-second);
display: flex;
.close {

View File

@@ -45,6 +45,6 @@ useDisableEventListener(() => props.modelValue)
width: 100%;
height: 100%;
display: flex;
background: var(--color-main-bg);
background: var(--color-primary);
}
</style>

View File

@@ -6,7 +6,6 @@ import {usePracticeStore} from "@/stores/practice.ts";
import {useSettingStore} from "@/stores/setting.ts";
import {usePlayBeep, usePlayCorrect, usePlayKeyboardAudio, usePlayWordAudio} from "@/hooks/sound.ts";
import {emitter, EventKey} from "@/utils/eventBus.ts";
import jq from 'jquery'
import {_nextTick} from "@/utils";
import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css'
import ContextMenu from '@imengyu/vue3-context-menu'
@@ -75,7 +74,6 @@ const store = useBaseStore()
const statisticsStore = usePracticeStore()
const settingStore = useSettingStore()
window.$ = jq
watch([() => sectionIndex, () => sentenceIndex, () => wordIndex, () => stringIndex], ([a, b, c,]) => {
checkCursorPosition(a, b, c)
})
@@ -97,18 +95,20 @@ watch(() => settingStore.translate, () => {
function checkCursorPosition(a = sectionIndex, b = sentenceIndex, c = wordIndex) {
// console.log('checkCursorPosition')
_nextTick(() => {
let currentWord = jq(`.section:nth(${a}) .sentence:nth(${b}) .word:nth(${c})`)
// console.log(a, b, c, currentWord)
if (currentWord.length) {
let end = currentWord.find('.word-end')
// console.log(end)
if (end.length) {
let articleRect = articleWrapperRef.getBoundingClientRect()
// 选中目标元素
const currentWord = document.querySelector(`.section:nth-of-type(${a + 1}) .sentence:nth-of-type(${b + 1}) .word:nth-of-type(${c + 1})`);
if (currentWord) {
// 在 currentWord 内找 .word-end
const end = currentWord.querySelector('.word-end');
if (end) {
// 获取 articleWrapper 的位置
const articleRect = articleWrapperRef.getBoundingClientRect();
const endRect = end.getBoundingClientRect();
// 计算相对位置
cursor = {
top: end.offset().top - articleRect.top,
left: end.offset().left - articleRect.left,
}
// console.log(cursor)
top: endRect.top - articleRect.top,
left: endRect.left - articleRect.left,
};
}
}
},)
@@ -548,7 +548,7 @@ let showQuestions = $ref(false)
}
.hover-show {
background: var(--color-main-active);
background: var(--color-select-bg);
color: white !important;
.wrote {
@@ -654,7 +654,7 @@ let showQuestions = $ref(false)
}
.word-start {
color: var(--color-main-active);
color: var(--color-select-bg);
}
.wrong {

View File

@@ -53,6 +53,7 @@ function next() {
}
function init() {
//todo 这个页面,直接访问白屏
if (!store.currentBook.articles.length) return
articleData.articles = cloneDeep(store.currentBook.articles)
getCurrentPractice()
@@ -436,8 +437,6 @@ const {playSentenceAudio} = usePlaySentenceAudio()
flex-direction: column;
justify-content: space-between;
align-items: center;
//padding-right: var(--practice-wrapper-padding-right);
transform: translateX(var(--practice-wrapper-translateX));
}
.swiper-wrapper {
@@ -501,7 +500,7 @@ const {playSentenceAudio} = usePlaySentenceAudio()
width: 100%;
box-sizing: border-box;
border-radius: .6rem;
background: var(--color-second-bg);
background: var(--color-second);
padding: .2rem var(--space) .4rem var(--space);
z-index: 2;
border: 1px solid var(--color-item-border);

View File

@@ -1,153 +0,0 @@
<script setup lang="ts">
import {Word} from "@/types.ts";
import VolumeIcon from "@/components/icon/VolumeIcon.vue";
import {usePlayWordAudio} from "@/hooks/sound.ts";
const props = withDefaults(defineProps<{
item: Word,
showTranslate?: boolean
showWord?: boolean
border?: boolean
}>(), {
showTranslate: true,
showWord: true,
border: true
})
const playWordAudio = usePlayWordAudio()
</script>
<template>
<div class="word-item"
:class="{
border,
}"
>
<div class="left">
<slot name="prefix" :item="item"></slot>
<div class="title-wrapper">
<div class="item-title">
<span class="word" :class="!showWord && 'word-shadow'">{{ item.word }}</span>
<span class="phonetic">{{ item.phonetic0 }}</span>
<VolumeIcon class="volume" @click="playWordAudio(item.word)"></VolumeIcon>
</div>
<div class="item-sub-title" v-if="item.trans.length && showTranslate">
<div v-for="v in item.trans">{{ (v.pos ? v.pos + '.' : '') + (v.cn) }}</div>
</div>
</div>
</div>
<div class="right">
<slot name="suffix" :item="item"></slot>
</div>
</div>
</template>
<style scoped lang="scss">
.word-item {
cursor: pointer;
width: 100%;
box-sizing: border-box;
background: var(--color-item-bg);
color: var(--color-font-1);
font-size: 1.1rem;
border-radius: .5rem;
display: flex;
justify-content: space-between;
transition: all .3s;
padding: .6rem;
gap: .6rem;
border: 1px solid var(--color-item-border);
.left {
display: flex;
gap: .6rem;
.title-wrapper {
display: flex;
flex-direction: column;
gap: .2rem;
word-break: break-word;
}
}
.right {
display: flex;
flex-direction: column;
gap: .3rem;
transition: all .3s;
}
.volume, .collect, .easy {
opacity: 0;
}
&:hover {
background: var(--color-item-hover);
.volume, .collect, .easy {
opacity: 1;
}
}
&.active {
background: var(--color-item-active);
$c: #E6A23C;
.phonetic, .item-sub-title {
color: var(--color-gray) !important;
}
.volume, .collect, .easy, .fill {
color: $c;
}
}
&.border {
&.active {
.item-title {
border-bottom: 2px solid gray !important;
}
}
.item-title {
transition: all .3s;
cursor: pointer;
border-bottom: 2px solid transparent;
}
&:hover {
.item-title {
border-bottom: 2px solid gray !important;
}
}
}
.item-title {
display: flex;
align-items: center;
gap: .5rem;
color: var(--color-font-1);
.word {
font-size: 1.2rem;
display: flex;
}
.phonetic {
font-size: .9rem;
color: gray;
}
}
.item-sub-title {
font-size: 1rem;
color: gray;
}
}
</style>

View File

@@ -4,7 +4,7 @@
<template>
<div class="flex justify-center">
<div class="w-[70vw] 2xl:w-[50vw] page">
<div class="page w-[70vw] 2xl:w-[50vw]">
<slot></slot>
</div>
</div>

View File

@@ -103,7 +103,7 @@ watch(() => settingStore.load, (n) => {
display: flex;
flex-direction: column;
align-items: center;
background: var(--color-second-bg);
background: var(--color-notice-bg);
padding: 1.8rem;
border-radius: 0.7rem;
width: 30rem;
@@ -124,7 +124,7 @@ watch(() => settingStore.load, (n) => {
}
.active {
color: var(--color-main-active);
color: var(--color-select-bg);
}
.wrapper {
@@ -148,7 +148,7 @@ watch(() => settingStore.load, (n) => {
display: flex;
align-items: center;
justify-content: space-between;
background: var(--color-main-bg);
background: var(--color-primary);
.href {
font-size: 0.9rem;
@@ -156,7 +156,7 @@ watch(() => settingStore.load, (n) => {
}
.star {
color: var(--color-main-active);
color: var(--color-select-bg);
}
.right {

View File

@@ -18,7 +18,6 @@ $w: 1.4rem;
border-radius: .3rem;
background: transparent;
transition: all .3s;
//color: var(--color-main-active);
&:hover {
background: var(--color-primary);
@@ -30,4 +29,4 @@ $w: 1.4rem;
height: $w;
}
}
</style>
</style>

View File

@@ -70,7 +70,7 @@ const vFocus = {
}
&.focus {
border: 1px solid var(--color-main-active);
border: 1px solid var(--color-select-bg);
:deep(svg) {
color: gray;

View File

@@ -219,7 +219,7 @@ $header-height: 3rem;
.panel {
border-radius: .5rem;
width: var(--panel-width);
background: var(--color-second-bg);
background: var(--color-second);
height: 100%;
display: flex;
flex-direction: column;
@@ -257,7 +257,7 @@ $header-height: 3rem;
color: gray;
&.active {
color: var(--color-main-active);
color: var(--color-select-bg);
font-weight: bold;
}
}

View File

@@ -499,7 +499,6 @@ function importData(e) {
}
.content {
background: var(--color-header-bg);
flex: 1;
height: 100%;
overflow: auto;
@@ -539,7 +538,7 @@ function importData(e) {
border: 1px solid gray;
border-radius: .2rem;
padding: 0 .3rem;
background: var(--color-second-bg);
background: var(--color-second);
color: var(--color-font-1);
}
}

View File

@@ -279,7 +279,7 @@ $header-height: 4rem;
.modal {
position: relative;
background: var(--color-second-bg);
background: var(--color-second);
overflow: hidden;
display: flex;
flex-direction: column;

View File

@@ -59,7 +59,7 @@ watch(() => props.modelValue, (n) => {
position: absolute;
z-index: 9;
width: 12rem;
background: var(--color-second-bg);
background: var(--color-second);
border-radius: .5rem;
box-shadow: 0 0 8px 2px var(--color-item-border);
padding: .6rem var(--space);

View File

@@ -203,4 +203,4 @@ defineExpose({scrollBottom})
}
}
}
</style>
</style>

View File

@@ -19,10 +19,10 @@ const {toggleTheme} = useTheme()
</script>
<template>
<div class="layout">
<div class="layout anim">
<!-- 第一个aside 占位用-->
<div class="aside space" :class="{'expand':settingStore.sideExpand}"></div>
<div class="aside fixed" :class="{'expand':settingStore.sideExpand}">
<div class="aside anim fixed" :class="{'expand':settingStore.sideExpand}">
<div class="top">
<Logo v-if="settingStore.sideExpand"/>
<div class="row" @click="router.push('/home')">
@@ -78,11 +78,11 @@ const {toggleTheme} = useTheme()
width: 100%;
height: 100%;
display: flex;
background: var(--color-background);
background: var(--color-primary);
}
.aside {
background: var(--color-second-bg);
background: var(--color-second);
height: 100vh;
padding: 1rem 1rem;
box-sizing: border-box;
@@ -91,7 +91,6 @@ const {toggleTheme} = useTheme()
justify-content: space-between;
box-shadow: rgb(0 0 0 / 3%) 0px 0px 12px 0px;
width: 4.5rem;
transition: all 0.3s;
z-index: 2;
.row {

View File

@@ -172,7 +172,7 @@ const progressTextRight = $computed(() => {
<div class="card flex gap-10">
<div class="flex-1 flex flex-col gap-2">
<div class="flex">
<div class="bg-slate-200 px-3 h-14 rounded-md flex items-center">
<div class="bg-third px-3 h-14 rounded-md flex items-center">
<span class="text-xl font-bold">{{ store.sdict.name || '请选择书籍开始学习' }}</span>
<BaseIcon title="切换词典" :icon="store.sdict.name ? 'gg:arrows-exchange' : 'fluent:add-20-filled'"
class="ml-4"
@@ -214,11 +214,12 @@ const progressTextRight = $computed(() => {
<div class="flex gap-1 items-center">
每日目标
<div style="color:#ac6ed1;" @click="setPerDayStudyNumber"
class="bg-slate-200 px-2 h-10 flex center text-2xl rounded cursor-pointer">
class="bg-third px-2 h-10 flex center text-2xl rounded cursor-pointer">
{{ store.sdict.id ? store.sdict.perDayStudyNumber : 0 }}
</div>
个单词 <span class="color-blue cursor-pointer" @click="setPerDayStudyNumber">更改</span>
</div>
<div class="btn">开始学习</div>
<div class="rounded-xl bg-slate-800 flex items-center gap-2 py-3 px-5 text-white cursor-pointer"
:class="store.sdict.name || 'opacity-70 cursor-not-allowed'" @click="startStudy">
<span>开始学习</span>

View File

@@ -206,7 +206,7 @@ const progress = $computed(() => {
width: 100%;
box-sizing: border-box;
border-radius: .6rem;
background: var(--color-second-bg);
background: var(--color-second);
padding: .2rem var(--space) .4rem var(--space);
z-index: 2;
border: 1px solid var(--color-item-border);

View File

@@ -1,23 +1,15 @@
// uno.config.ts
import {defineConfig, presetUno} from 'unocss'
import {defineConfig, presetWind3} from 'unocss'
export default defineConfig({
content: {
pipeline: {
include: [
'./src/**/*.{html,vue,ts,js}',
'./index.html',
],
exclude: [
'./node_modules/**/*',
'./dist/**/*',
'./.pnpm/**/*',
'./.output/**/*',
],
},
shortcuts: {
'bg-primary': 'bg-[var(--color-primary)]',
'bg-second': 'bg-[var(--color-second)]',
'bg-third': 'bg-[var(--color-third)]',
'bg-card-active': 'bg-[var(--color-card-active)]',
'color-main': 'color-[var(--color-main-text)]',
},
presets: [
presetUno(),
presetWind3(),
],
})

View File

@@ -58,11 +58,6 @@ async function getConfig() {
var: 'VueRouter',
path: `https://cdn.jsdelivr.net/npm/vue-router@4.5.1/dist/vue-router.global.prod.min.js`
},
{
name: 'jquery',
var: 'jQuery',
path: 'https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js'
},
// {
// name: 'axios',
// var: 'axios',
@@ -92,7 +87,7 @@ async function getConfig() {
},
server: {
port: 3000,
open: true,
open: false,
host: '0.0.0.0',
fs: {
strict: false,