mirror of
https://github.com/doocs/md.git
synced 2025-01-23 04:14:42 +08:00
refactor: remove element-plus (#456)
This commit is contained in:
parent
475181b483
commit
07bf1b909d
195
package-lock.json
generated
195
package-lock.json
generated
@ -9,7 +9,6 @@
|
|||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.3.1",
|
|
||||||
"@vueuse/core": "^12.0.0",
|
"@vueuse/core": "^12.0.0",
|
||||||
"axios": "^1.7.8",
|
"axios": "^1.7.8",
|
||||||
"buffer-from": "^1.1.2",
|
"buffer-from": "^1.1.2",
|
||||||
@ -20,7 +19,6 @@
|
|||||||
"cos-js-sdk-v5": "^1.8.6",
|
"cos-js-sdk-v5": "^1.8.6",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"csstype": "^3.1.3",
|
"csstype": "^3.1.3",
|
||||||
"element-plus": "^2.8.8",
|
|
||||||
"es-toolkit": "^1.27.0",
|
"es-toolkit": "^1.27.0",
|
||||||
"form-data": "4.0.1",
|
"form-data": "4.0.1",
|
||||||
"highlight.js": "^11.10.0",
|
"highlight.js": "^11.10.0",
|
||||||
@ -37,7 +35,9 @@
|
|||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"tiny-oss": "^0.5.1",
|
"tiny-oss": "^0.5.1",
|
||||||
"uuid": "^11.0.3",
|
"uuid": "^11.0.3",
|
||||||
"vue": "^3.5.13"
|
"vue": "^3.5.13",
|
||||||
|
"vue-pick-colors": "^1.7.8",
|
||||||
|
"vue-sonner": "^1.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "3.11.0",
|
"@antfu/eslint-config": "3.11.0",
|
||||||
@ -820,14 +820,6 @@
|
|||||||
"sisteransi": "^1.0.5"
|
"sisteransi": "^1.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ctrl/tinycolor": {
|
|
||||||
"version": "3.6.1",
|
|
||||||
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
|
|
||||||
"integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@devicefarmer/adbkit": {
|
"node_modules/@devicefarmer/adbkit": {
|
||||||
"version": "3.2.6",
|
"version": "3.2.6",
|
||||||
"resolved": "https://registry.npmmirror.com/@devicefarmer/adbkit/-/adbkit-3.2.6.tgz",
|
"resolved": "https://registry.npmmirror.com/@devicefarmer/adbkit/-/adbkit-3.2.6.tgz",
|
||||||
@ -898,14 +890,6 @@
|
|||||||
"integrity": "sha512-zQ42I53sb4WVHA+5yoY1t59Zk++Ot02AvUgtNKLzTT8mPyVqVChFcePa3on/xIoKEgH+RoepgPHzqfk9837YFw==",
|
"integrity": "sha512-zQ42I53sb4WVHA+5yoY1t59Zk++Ot02AvUgtNKLzTT8mPyVqVChFcePa3on/xIoKEgH+RoepgPHzqfk9837YFw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@element-plus/icons-vue": {
|
|
||||||
"version": "2.3.1",
|
|
||||||
"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
|
|
||||||
"integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"vue": "^3.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@es-joy/jsdoccomment": {
|
"node_modules/@es-joy/jsdoccomment": {
|
||||||
"version": "0.48.0",
|
"version": "0.48.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@es-joy/jsdoccomment/-/jsdoccomment-0.48.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@es-joy/jsdoccomment/-/jsdoccomment-0.48.0.tgz",
|
||||||
@ -2614,19 +2598,6 @@
|
|||||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/lodash": {
|
|
||||||
"version": "4.17.13",
|
|
||||||
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.13.tgz",
|
|
||||||
"integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg=="
|
|
||||||
},
|
|
||||||
"node_modules/@types/lodash-es": {
|
|
||||||
"version": "4.17.12",
|
|
||||||
"resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz",
|
|
||||||
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/lodash": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/mdast": {
|
"node_modules/@types/mdast": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-4.0.4.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-4.0.4.tgz",
|
||||||
@ -3880,11 +3851,6 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/async-validator": {
|
|
||||||
"version": "4.2.5",
|
|
||||||
"resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
|
|
||||||
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="
|
|
||||||
},
|
|
||||||
"node_modules/asynckit": {
|
"node_modules/asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
@ -6561,119 +6527,6 @@
|
|||||||
"integrity": "sha512-pI2QF6+i+zjPbqRzJwkMvtvkdI7MjVbSh2g8dlMguDJIXEPw+kwasS1Jl+YGPEBfGVxsVgGUratAKymPdPo2vQ==",
|
"integrity": "sha512-pI2QF6+i+zjPbqRzJwkMvtvkdI7MjVbSh2g8dlMguDJIXEPw+kwasS1Jl+YGPEBfGVxsVgGUratAKymPdPo2vQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/element-plus": {
|
|
||||||
"version": "2.8.8",
|
|
||||||
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.8.8.tgz",
|
|
||||||
"integrity": "sha512-MLAH1x2PGTnOT7Iwqh9ASgfZhvgqQqrdbxuJH0w2fGjzE4ZjryyLQj24HXoQO7Zon66U3lrYxbdLI57M6OX0qw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@ctrl/tinycolor": "^3.4.1",
|
|
||||||
"@element-plus/icons-vue": "^2.3.1",
|
|
||||||
"@floating-ui/dom": "^1.0.1",
|
|
||||||
"@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
|
|
||||||
"@types/lodash": "^4.14.182",
|
|
||||||
"@types/lodash-es": "^4.17.6",
|
|
||||||
"@vueuse/core": "^9.1.0",
|
|
||||||
"async-validator": "^4.2.5",
|
|
||||||
"dayjs": "^1.11.13",
|
|
||||||
"escape-html": "^1.0.3",
|
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"lodash-es": "^4.17.21",
|
|
||||||
"lodash-unified": "^1.0.2",
|
|
||||||
"memoize-one": "^6.0.0",
|
|
||||||
"normalize-wheel-es": "^1.2.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"vue": "^3.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/element-plus/node_modules/@types/web-bluetooth": {
|
|
||||||
"version": "0.0.16",
|
|
||||||
"resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
|
|
||||||
"integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ=="
|
|
||||||
},
|
|
||||||
"node_modules/element-plus/node_modules/@vueuse/core": {
|
|
||||||
"version": "9.13.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz",
|
|
||||||
"integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/web-bluetooth": "^0.0.16",
|
|
||||||
"@vueuse/metadata": "9.13.0",
|
|
||||||
"@vueuse/shared": "9.13.0",
|
|
||||||
"vue-demi": "*"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/antfu"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/element-plus/node_modules/@vueuse/core/node_modules/vue-demi": {
|
|
||||||
"version": "0.14.10",
|
|
||||||
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
|
|
||||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"bin": {
|
|
||||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
|
||||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/antfu"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@vue/composition-api": "^1.0.0-rc.1",
|
|
||||||
"vue": "^3.0.0-0 || ^2.6.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@vue/composition-api": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/element-plus/node_modules/@vueuse/metadata": {
|
|
||||||
"version": "9.13.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz",
|
|
||||||
"integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/antfu"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/element-plus/node_modules/@vueuse/shared": {
|
|
||||||
"version": "9.13.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz",
|
|
||||||
"integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==",
|
|
||||||
"dependencies": {
|
|
||||||
"vue-demi": "*"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/antfu"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/element-plus/node_modules/@vueuse/shared/node_modules/vue-demi": {
|
|
||||||
"version": "0.14.10",
|
|
||||||
"resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
|
|
||||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"bin": {
|
|
||||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
|
||||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/antfu"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@vue/composition-api": "^1.0.0-rc.1",
|
|
||||||
"vue": "^3.0.0-0 || ^2.6.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@vue/composition-api": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/elliptic": {
|
"node_modules/elliptic": {
|
||||||
"version": "6.6.1",
|
"version": "6.6.1",
|
||||||
"resolved": "https://registry.npmmirror.com/elliptic/-/elliptic-6.6.1.tgz",
|
"resolved": "https://registry.npmmirror.com/elliptic/-/elliptic-6.6.1.tgz",
|
||||||
@ -6993,11 +6846,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/escape-html": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
|
||||||
},
|
|
||||||
"node_modules/escape-string-regexp": {
|
"node_modules/escape-string-regexp": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
@ -10420,16 +10268,6 @@
|
|||||||
"resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
|
"resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||||
},
|
},
|
||||||
"node_modules/lodash-unified": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/lodash-es": "*",
|
|
||||||
"lodash": "*",
|
|
||||||
"lodash-es": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lodash.camelcase": {
|
"node_modules/lodash.camelcase": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
"resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||||
@ -10985,11 +10823,6 @@
|
|||||||
"integrity": "sha512-rsfnCbOHjqrhWxwt5/wtSLzpoKTzW7OXdT5lLOIH1OTYhWu9rRJveGq0sKvDZODABH7RX+uoR+DYcpFnq4Tf6Q==",
|
"integrity": "sha512-rsfnCbOHjqrhWxwt5/wtSLzpoKTzW7OXdT5lLOIH1OTYhWu9rRJveGq0sKvDZODABH7RX+uoR+DYcpFnq4Tf6Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/memoize-one": {
|
|
||||||
"version": "6.0.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
|
|
||||||
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
|
|
||||||
},
|
|
||||||
"node_modules/memorystream": {
|
"node_modules/memorystream": {
|
||||||
"version": "0.3.1",
|
"version": "0.3.1",
|
||||||
"resolved": "https://registry.npmmirror.com/memorystream/-/memorystream-0.3.1.tgz",
|
"resolved": "https://registry.npmmirror.com/memorystream/-/memorystream-0.3.1.tgz",
|
||||||
@ -12261,11 +12094,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/normalize-wheel-es": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw=="
|
|
||||||
},
|
|
||||||
"node_modules/npm-run-all": {
|
"node_modules/npm-run-all": {
|
||||||
"version": "4.1.5",
|
"version": "4.1.5",
|
||||||
"resolved": "https://registry.npmmirror.com/npm-run-all/-/npm-run-all-4.1.5.tgz",
|
"resolved": "https://registry.npmmirror.com/npm-run-all/-/npm-run-all-4.1.5.tgz",
|
||||||
@ -17460,6 +17288,23 @@
|
|||||||
"vue": "^3.4.37"
|
"vue": "^3.4.37"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-pick-colors": {
|
||||||
|
"version": "1.7.8",
|
||||||
|
"resolved": "https://registry.npmmirror.com/vue-pick-colors/-/vue-pick-colors-1.7.8.tgz",
|
||||||
|
"integrity": "sha512-6AIMrjh+q+CYmhgyDUh062qLR9ua0pWFGlBnPD7egT+CEJslThQFNsc1NQ0+KUmgxtGukSGTuI7CkC/eHxbB5w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@popperjs/core": "^2.11.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@popperjs/core": "^2.11.2",
|
||||||
|
"vue": "^3.2.26"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vue-sonner": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/vue-sonner/-/vue-sonner-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-jAodBy4Mri8rQjVZGQAPs4ZYymc1ywPiwfa81qU0fFl+Suk7U8NaOxIDdI1oBGLeQJqRZi/oxNIuhCLqsBmOwg=="
|
||||||
|
},
|
||||||
"node_modules/vue-tsc": {
|
"node_modules/vue-tsc": {
|
||||||
"version": "2.1.10",
|
"version": "2.1.10",
|
||||||
"resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-2.1.10.tgz",
|
"resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-2.1.10.tgz",
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
"postinstall": "simple-git-hooks && wxt prepare"
|
"postinstall": "simple-git-hooks && wxt prepare"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.3.1",
|
|
||||||
"@vueuse/core": "^12.0.0",
|
"@vueuse/core": "^12.0.0",
|
||||||
"axios": "^1.7.8",
|
"axios": "^1.7.8",
|
||||||
"buffer-from": "^1.1.2",
|
"buffer-from": "^1.1.2",
|
||||||
@ -32,7 +31,6 @@
|
|||||||
"cos-js-sdk-v5": "^1.8.6",
|
"cos-js-sdk-v5": "^1.8.6",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"csstype": "^3.1.3",
|
"csstype": "^3.1.3",
|
||||||
"element-plus": "^2.8.8",
|
|
||||||
"es-toolkit": "^1.27.0",
|
"es-toolkit": "^1.27.0",
|
||||||
"form-data": "4.0.1",
|
"form-data": "4.0.1",
|
||||||
"highlight.js": "^11.10.0",
|
"highlight.js": "^11.10.0",
|
||||||
@ -49,7 +47,9 @@
|
|||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"tiny-oss": "^0.5.1",
|
"tiny-oss": "^0.5.1",
|
||||||
"uuid": "^11.0.3",
|
"uuid": "^11.0.3",
|
||||||
"vue": "^3.5.13"
|
"vue": "^3.5.13",
|
||||||
|
"vue-pick-colors": "^1.7.8",
|
||||||
|
"vue-sonner": "^1.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "3.11.0",
|
"@antfu/eslint-config": "3.11.0",
|
||||||
|
10
src/App.vue
10
src/App.vue
@ -72,14 +72,4 @@ body {
|
|||||||
background: #f0f0f0;
|
background: #f0f0f0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修复分栏线负数 margin 导致的轴向滚动条
|
|
||||||
.el-dropdown-menu__item--divided:before {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修复颜色选择器下拉箭头位置
|
|
||||||
.el-icon.el-color-picker__icon.is-icon-arrow-down {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -34,11 +34,6 @@ section {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.el-message__icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.web-title {
|
.web-title {
|
||||||
margin: 0 15px 0 5px;
|
margin: 0 15px 0 5px;
|
||||||
}
|
}
|
||||||
|
@ -1,57 +1,99 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@/components/ui/alert-dialog'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
import { useDisplayStore, useStore } from '@/stores'
|
import { useDisplayStore, useStore } from '@/stores'
|
||||||
|
import { Edit3, Plus, X } from 'lucide-vue-next'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ref } from 'vue'
|
||||||
|
import { toast } from 'vue-sonner'
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const displayStore = useDisplayStore()
|
const displayStore = useDisplayStore()
|
||||||
|
|
||||||
function editTabName() {
|
const isOpenEditDialog = ref(false)
|
||||||
ElMessageBox.prompt(`请输入新的方案名称`, `编辑方案名称`, {
|
const editInputVal = ref(``)
|
||||||
confirmButtonText: `确认`,
|
const tabHistory = ref([``, store.cssContentConfig.active])
|
||||||
cancelButtonText: `取消`,
|
|
||||||
inputValue: store.cssContentConfig.active,
|
function rename(name: string) {
|
||||||
inputErrorMessage: `不能与现有方案重名`,
|
editInputVal.value = name
|
||||||
inputValidator: store.validatorTabName,
|
isOpenEditDialog.value = true
|
||||||
})
|
|
||||||
.then(({ value }) => {
|
|
||||||
if (!(`${value}`).trim()) {
|
|
||||||
ElMessage.error(`修改失败,方案名不可为空`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
store.renameTab(value)
|
|
||||||
ElMessage.success(`修改成功~`)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTabsEdit(targetName: string, action: string) {
|
function editTabName() {
|
||||||
if (action === `add`) {
|
if (!(editInputVal.value).trim()) {
|
||||||
ElMessageBox.prompt(`请输入方案名称`, `新建自定义 CSS`, {
|
toast.error(`新建失败,方案名不可为空`)
|
||||||
confirmButtonText: `确认`,
|
|
||||||
cancelButtonText: `取消`,
|
|
||||||
inputValue: `方案${store.cssContentConfig.tabs.length + 1}`,
|
|
||||||
inputErrorMessage: `不能与现有方案重名`,
|
|
||||||
inputValidator: store.validatorTabName,
|
|
||||||
})
|
|
||||||
.then(({ value }) => {
|
|
||||||
if (!(`${value}`).trim()) {
|
|
||||||
ElMessage.error(`新建失败,方案名不可为空`)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
store.addCssContentTab(value)
|
|
||||||
ElMessage.success(`新建成功~`)
|
if (!store.validatorTabName(editInputVal.value)) {
|
||||||
})
|
toast.error(`不能与现有方案重名`)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
else if (action === `remove`) {
|
store.renameTab(editInputVal.value)
|
||||||
|
isOpenEditDialog.value = false
|
||||||
|
toast.success(`修改成功~`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isOpenAddDialog = ref(false)
|
||||||
|
|
||||||
|
const addInputVal = ref(``)
|
||||||
|
|
||||||
|
function addTab() {
|
||||||
|
if (!(addInputVal.value).trim()) {
|
||||||
|
toast.error(`新建失败,方案名不可为空`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!store.validatorTabName(addInputVal.value)) {
|
||||||
|
toast.error(`不能与现有方案重名`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
store.addCssContentTab(addInputVal.value)
|
||||||
|
isOpenAddDialog.value = false
|
||||||
|
|
||||||
|
store.cssContentConfig.active = addInputVal.value
|
||||||
|
tabHistory.value = [tabHistory.value[1], addInputVal.value]
|
||||||
|
toast.success(`新建成功~`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isOpenDelTabConfirmDialog = ref(false)
|
||||||
|
const delTargetName = ref(``)
|
||||||
|
|
||||||
|
function removeHandler(targetName: string) {
|
||||||
|
delTargetName.value = targetName
|
||||||
|
isOpenDelTabConfirmDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function delTab() {
|
||||||
const tabs = store.cssContentConfig.tabs
|
const tabs = store.cssContentConfig.tabs
|
||||||
if (tabs.length === 1) {
|
if (tabs.length === 1) {
|
||||||
ElMessage.warning(`至少保留一个方案`)
|
toast.warning(`至少保留一个方案`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let activeName = store.cssContentConfig.active
|
let activeName = store.cssContentConfig.active
|
||||||
if (activeName === targetName) {
|
if (activeName === delTargetName.value) {
|
||||||
tabs.forEach((tab, index) => {
|
tabs.forEach((tab, index) => {
|
||||||
if (tab.name === targetName) {
|
if (tab.name === delTargetName.value) {
|
||||||
const nextTab = tabs[index + 1] || tabs[index - 1]
|
const nextTab = tabs[index + 1] || tabs[index - 1]
|
||||||
if (nextTab) {
|
if (nextTab) {
|
||||||
activeName = nextTab.name
|
activeName = nextTab.name
|
||||||
@ -61,45 +103,121 @@ function handleTabsEdit(targetName: string, action: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
store.tabChanged(activeName)
|
store.tabChanged(activeName)
|
||||||
store.cssContentConfig.tabs = tabs.filter(tab => tab.name !== targetName)
|
store.cssContentConfig.tabs = tabs.filter(tab => tab.name !== delTargetName.value)
|
||||||
|
|
||||||
|
toast.success(`删除成功~`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function addHandler() {
|
||||||
|
addInputVal.value = `方案${store.cssContentConfig.tabs.length + 1}`
|
||||||
|
isOpenAddDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function tabChanged(tabName: string | number) {
|
||||||
|
if (tabName === `add`) {
|
||||||
|
store.cssContentConfig.active = tabHistory.value[1]
|
||||||
|
addHandler()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tabHistory.value = [tabHistory.value[1], tabName as string]
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<transition enter-active-class="bounceInRight">
|
<transition enter-active-class="bounceInRight">
|
||||||
<el-col v-show="displayStore.isShowCssEditor" :span="8" class="cssEditor-wrapper order-1 h-full flex flex-col border-l-1">
|
<div v-show="displayStore.isShowCssEditor" class="cssEditor-wrapper order-1 h-full flex flex-col border-l-1">
|
||||||
<el-tabs
|
<Tabs
|
||||||
v-model="store.cssContentConfig.active"
|
v-model="store.cssContentConfig.active"
|
||||||
type="border-card"
|
@update:model-value="tabChanged"
|
||||||
stretch
|
|
||||||
editable
|
|
||||||
@edit="handleTabsEdit"
|
|
||||||
@tab-change="store.tabChanged"
|
|
||||||
>
|
>
|
||||||
<el-tab-pane
|
<TabsList class="w-full overflow-x-auto">
|
||||||
|
<TabsTrigger
|
||||||
v-for="item in store.cssContentConfig.tabs"
|
v-for="item in store.cssContentConfig.tabs"
|
||||||
:key="item.name"
|
:key="item.name"
|
||||||
:name="item.name"
|
:value="item.name"
|
||||||
|
class="flex-1"
|
||||||
>
|
>
|
||||||
<template #label>
|
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
<el-icon
|
<Edit3
|
||||||
v-if="store.cssContentConfig.active === item.name"
|
v-show="store.cssContentConfig.active === item.name" class="inline size-4 rounded-full p-0.5 transition-color hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||||
class="ml-1"
|
@click="rename(item.name)"
|
||||||
@click="editTabName()"
|
/>
|
||||||
>
|
<X
|
||||||
<ElIconEditPen />
|
v-show="store.cssContentConfig.active === item.name" class="inline size-4 rounded-full p-0.5 transition-color hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||||
</el-icon>
|
@click.self="removeHandler(item.name)"
|
||||||
</template>
|
/>
|
||||||
</el-tab-pane>
|
</TabsTrigger>
|
||||||
</el-tabs>
|
<TabsTrigger value="add">
|
||||||
|
<Plus class="h-5 w-5" />
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
<textarea
|
<textarea
|
||||||
id="cssEditor"
|
id="cssEditor"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
placeholder="Your custom css here."
|
placeholder="Your custom css here."
|
||||||
/>
|
/>
|
||||||
</el-col>
|
|
||||||
|
<!-- 新增弹窗 -->
|
||||||
|
<Dialog v-model:open="isOpenAddDialog">
|
||||||
|
<DialogContent class="sm:max-w-[425px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>新建自定义 CSS</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
请输入方案名称
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<Input v-model="addInputVal" />
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" @click="isOpenAddDialog = false">
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button @click="addTab()">
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<!-- 重命名弹窗 -->
|
||||||
|
<Dialog v-model:open="isOpenEditDialog">
|
||||||
|
<DialogContent class="sm:max-w-[425px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>编辑方案名称</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
请输入新的方案名称
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<Input v-model="editInputVal" />
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" @click="isOpenEditDialog = false">
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
<Button @click="editTabName">
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<AlertDialog v-model:open="isOpenDelTabConfirmDialog">
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>提示</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
此操作将删除该自定义方案,是否继续?
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||||
|
<AlertDialogAction @click="delTab">
|
||||||
|
确认
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -141,19 +259,4 @@ function handleTabsEdit(targetName: string, action: string) {
|
|||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-tabs__content) {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 当 tab 为激活状态时,隐藏关闭按钮
|
|
||||||
:deep(.el-tabs__item.is-active) {
|
|
||||||
.is-icon-close {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-tabs__new-tab) {
|
|
||||||
margin-right: 1em;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useStore } from '@/stores'
|
import { useStore } from '@/stores'
|
||||||
|
import { Download, FileCode, Upload } from 'lucide-vue-next'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
@ -11,8 +11,6 @@ const {
|
|||||||
} = storeToRefs(store)
|
} = storeToRefs(store)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
toggleDark,
|
|
||||||
toggleEditOnLeft,
|
|
||||||
exportEditorContent2HTML,
|
exportEditorContent2HTML,
|
||||||
exportEditorContent2MD,
|
exportEditorContent2MD,
|
||||||
importMarkdownContent,
|
importMarkdownContent,
|
||||||
@ -26,37 +24,25 @@ const {
|
|||||||
</MenubarTrigger>
|
</MenubarTrigger>
|
||||||
<MenubarContent align="start">
|
<MenubarContent align="start">
|
||||||
<MenubarItem @click="importMarkdownContent()">
|
<MenubarItem @click="importMarkdownContent()">
|
||||||
<el-icon class="mr-2 h-4 w-4">
|
<Upload class="mr-2 size-4" />
|
||||||
<ElIconUpload />
|
|
||||||
</el-icon>
|
|
||||||
导入 .md
|
导入 .md
|
||||||
</MenubarItem>
|
</MenubarItem>
|
||||||
<MenubarItem @click="exportEditorContent2MD()">
|
<MenubarItem @click="exportEditorContent2MD()">
|
||||||
<el-icon class="mr-2 h-4 w-4">
|
<Download class="mr-2 size-4" />
|
||||||
<ElIconDownload />
|
|
||||||
</el-icon>
|
|
||||||
导出 .md
|
导出 .md
|
||||||
</MenubarItem>
|
</MenubarItem>
|
||||||
<MenubarItem @click="exportEditorContent2HTML()">
|
<MenubarItem @click="exportEditorContent2HTML()">
|
||||||
<el-icon class="mr-2 h-4 w-4">
|
<FileCode class="mr-2 size-4" />
|
||||||
<ElIconDocument />
|
|
||||||
</el-icon>
|
|
||||||
导出 .html
|
导出 .html
|
||||||
</MenubarItem>
|
</MenubarItem>
|
||||||
<MenubarSeparator />
|
<MenubarSeparator />
|
||||||
<MenubarItem @click="toggleDark()">
|
<MenubarCheckboxItem v-model:checked="isDark">
|
||||||
<el-icon class="mr-2 h-4 w-4" :class="{ 'opacity-0': !isDark }">
|
|
||||||
<ElIconCheck />
|
|
||||||
</el-icon>
|
|
||||||
深色模式
|
深色模式
|
||||||
</MenubarItem>
|
</MenubarCheckboxItem>
|
||||||
<MenubarSeparator />
|
<MenubarSeparator />
|
||||||
<MenubarItem @click="toggleEditOnLeft()">
|
<MenubarCheckboxItem v-model:checked="isEditOnLeft">
|
||||||
<el-icon class="mr-2 h-4 w-4" :class="{ 'opacity-0': !isEditOnLeft }">
|
|
||||||
<ElIconCheck />
|
|
||||||
</el-icon>
|
|
||||||
左侧编辑
|
左侧编辑
|
||||||
</MenubarItem>
|
</MenubarCheckboxItem>
|
||||||
</MenubarContent>
|
</MenubarContent>
|
||||||
</MenubarMenu>
|
</MenubarMenu>
|
||||||
</template>
|
</template>
|
||||||
|
@ -12,10 +12,9 @@ const aboutDialogVisible = ref(false)
|
|||||||
帮助
|
帮助
|
||||||
</MenubarTrigger>
|
</MenubarTrigger>
|
||||||
<MenubarContent align="start">
|
<MenubarContent align="start">
|
||||||
<MenubarItem @click="aboutDialogVisible = true">
|
<MenubarCheckboxItem @click="aboutDialogVisible = true">
|
||||||
<el-icon class="mr-2 h-4 w-4" />
|
|
||||||
<span>关于</span>
|
<span>关于</span>
|
||||||
</MenubarItem>
|
</MenubarCheckboxItem>
|
||||||
</MenubarContent>
|
</MenubarContent>
|
||||||
</MenubarMenu>
|
</MenubarMenu>
|
||||||
|
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
} from '@/components/ui/dialog'
|
} from '@/components/ui/dialog'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import { Textarea } from '@/components/ui/textarea'
|
||||||
import { useStore } from '@/stores'
|
import { useStore } from '@/stores'
|
||||||
|
import { Info } from 'lucide-vue-next'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
@ -44,7 +50,6 @@ function prePost() {
|
|||||||
...auto,
|
...auto,
|
||||||
auto,
|
auto,
|
||||||
}
|
}
|
||||||
dialogVisible.value = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@ -72,38 +77,42 @@ function onUpdate(val: boolean) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<Dialog v-model:open="dialogVisible" @update:open="onUpdate">
|
||||||
|
<DialogTrigger>
|
||||||
<Button variant="outline" @click="prePost">
|
<Button variant="outline" @click="prePost">
|
||||||
发布
|
发布
|
||||||
</Button>
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
<Dialog :open="dialogVisible" @update:open="onUpdate">
|
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>发布</DialogTitle>
|
<DialogTitle>发布</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
<Alert>
|
||||||
|
<Info class="h-4 w-4" />
|
||||||
|
<AlertTitle>提示</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
此功能由第三方浏览器插件支持,本平台不保证安全性。
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
<el-alert
|
<div class="w-full flex items-center gap-4">
|
||||||
class="mb-4"
|
<Label for="thumb" class="w-10 text-end">
|
||||||
title="注:此功能由第三方浏览器插件支持,本平台不保证安全性。"
|
封面
|
||||||
type="info"
|
</Label>
|
||||||
show-icon
|
<Input id="thumb" v-model="form.thumb" placeholder="自动提取第一张图" />
|
||||||
/>
|
</div>
|
||||||
<el-form class="postInfo" label-width="50" :model="form">
|
<div class="w-full flex items-center gap-4">
|
||||||
<el-form-item label="封面">
|
<Label for="title" class="w-10 text-end">
|
||||||
<el-input v-model="form.thumb" placeholder="自动提取第一张图" />
|
标题
|
||||||
</el-form-item>
|
</Label>
|
||||||
<el-form-item label="标题">
|
<Input id="title" v-model="form.title" placeholder="自动提取第一个标题" />
|
||||||
<el-input v-model="form.title" placeholder="自动提取第一个标题" />
|
</div>
|
||||||
</el-form-item>
|
<div class="w-full flex items-start gap-4">
|
||||||
<el-form-item label="描述">
|
<Label for="desc" class="w-10 text-end">
|
||||||
<el-input
|
描述
|
||||||
v-model="form.desc"
|
</Label>
|
||||||
type="textarea"
|
<Textarea id="desc" v-model="form.desc" placeholder="自动提取第一个段落" />
|
||||||
:rows="4"
|
</div>
|
||||||
placeholder="自动提取第一个段落"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" @click="dialogVisible = false">
|
<Button variant="outline" @click="dialogVisible = false">
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
HoverCardContent,
|
HoverCardContent,
|
||||||
HoverCardTrigger,
|
HoverCardTrigger,
|
||||||
} from '@/components/ui/hover-card'
|
} from '@/components/ui/hover-card'
|
||||||
|
import { MenubarCheckboxItem } from '@/components/ui/menubar'
|
||||||
import {
|
import {
|
||||||
codeBlockThemeOptions,
|
codeBlockThemeOptions,
|
||||||
colorOptions,
|
colorOptions,
|
||||||
@ -12,10 +13,10 @@ import {
|
|||||||
legendOptions,
|
legendOptions,
|
||||||
themeOptions,
|
themeOptions,
|
||||||
} from '@/config'
|
} from '@/config'
|
||||||
|
|
||||||
import { useDisplayStore, useStore } from '@/stores'
|
import { useDisplayStore, useStore } from '@/stores'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { ref } from 'vue'
|
import { ref, useTemplateRef } from 'vue'
|
||||||
|
import PickColors, { type Format } from 'vue-pick-colors'
|
||||||
import StyleOptionMenu from './StyleOptionMenu.vue'
|
import StyleOptionMenu from './StyleOptionMenu.vue'
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
@ -56,6 +57,10 @@ function customStyle() {
|
|||||||
cssEditor.value!.refresh()
|
cssEditor.value!.refresh()
|
||||||
}, 50)
|
}, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pickColorsContainer = useTemplateRef(`pickColorsContainer`)
|
||||||
|
const format = ref<Format>(`rgb`)
|
||||||
|
const formatOptions = ref<Format[]>([`rgb`, `hex`, `hsl`, `hsv`])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -100,54 +105,36 @@ function customStyle() {
|
|||||||
:change="legendChanged"
|
:change="legendChanged"
|
||||||
/>
|
/>
|
||||||
<MenubarSeparator />
|
<MenubarSeparator />
|
||||||
<MenubarItem @click.self.prevent="showPicker">
|
<MenubarCheckboxItem @click.self.prevent="showPicker">
|
||||||
<HoverCard :open-delay="100">
|
<HoverCard :open-delay="100">
|
||||||
<HoverCardTrigger class="w-full flex">
|
<HoverCardTrigger class="w-full flex">
|
||||||
<el-icon class="mr-2 h-4 w-4" />
|
|
||||||
自定义主题色
|
自定义主题色
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
<HoverCardContent side="right" class="w-min">
|
<HoverCardContent side="right" class="w-min">
|
||||||
<ElColorPicker
|
<div ref="pickColorsContainer">
|
||||||
ref="colorPicker"
|
<PickColors
|
||||||
v-model="primaryColor"
|
v-model:value="primaryColor"
|
||||||
:teleported="false"
|
|
||||||
show-alpha
|
show-alpha
|
||||||
class="ml-auto"
|
:format="format" :format-options="formatOptions"
|
||||||
style="height: 2em"
|
:theme="store.isDark ? 'dark' : 'light'"
|
||||||
@change="colorChanged"
|
:popup-container="pickColorsContainer!"
|
||||||
@click="showPicker"
|
@change="store.colorChanged"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</HoverCardContent>
|
</HoverCardContent>
|
||||||
</HoverCard>
|
</HoverCard>
|
||||||
<!-- <el-icon class="mr-2 h-4 w-4" />
|
</MenubarCheckboxItem>
|
||||||
自定义主题色
|
<MenubarCheckboxItem @click="customStyle">
|
||||||
<el-color-picker
|
|
||||||
ref="colorPicker"
|
|
||||||
v-model="primaryColor"
|
|
||||||
:teleported="false"
|
|
||||||
show-alpha
|
|
||||||
class="ml-auto"
|
|
||||||
style="height: 2em"
|
|
||||||
@change="colorChanged"
|
|
||||||
@click="showPicker"
|
|
||||||
/> -->
|
|
||||||
</MenubarItem>
|
|
||||||
<MenubarItem @click="customStyle">
|
|
||||||
<el-icon class="mr-2 h-4 w-4" />
|
|
||||||
自定义 CSS
|
自定义 CSS
|
||||||
</MenubarItem>
|
</MenubarCheckboxItem>
|
||||||
<MenubarSeparator />
|
<MenubarSeparator />
|
||||||
<MenubarItem @click="macCodeBlockChanged">
|
<MenubarCheckboxItem :checked="isMacCodeBlock" @click="macCodeBlockChanged">
|
||||||
<el-icon class="mr-2 h-4 w-4" :class="{ 'opacity-0': !isMacCodeBlock }">
|
|
||||||
<ElIconCheck />
|
|
||||||
</el-icon>
|
|
||||||
Mac 代码块
|
Mac 代码块
|
||||||
</MenubarItem>
|
</MenubarCheckboxItem>
|
||||||
<MenubarSeparator />
|
<MenubarSeparator />
|
||||||
<MenubarItem divided @click="resetStyleConfirm">
|
<MenubarCheckboxItem divided @click="resetStyleConfirm">
|
||||||
<el-icon class="mr-2 h-4 w-4" />
|
|
||||||
重置
|
重置
|
||||||
</MenubarItem>
|
</MenubarCheckboxItem>
|
||||||
</MenubarContent>
|
</MenubarContent>
|
||||||
</MenubarMenu>
|
</MenubarMenu>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { IConfigOption } from '@/types'
|
import type { IConfigOption } from '@/types'
|
||||||
import {
|
import {
|
||||||
MenubarItem,
|
MenubarCheckboxItem,
|
||||||
MenubarSub,
|
MenubarSub,
|
||||||
MenubarSubContent,
|
MenubarSubContent,
|
||||||
MenubarSubTrigger,
|
MenubarSubTrigger,
|
||||||
@ -31,26 +31,24 @@ function setStyle(title: string, value: string) {
|
|||||||
<template>
|
<template>
|
||||||
<MenubarSub>
|
<MenubarSub>
|
||||||
<MenubarSubTrigger>
|
<MenubarSubTrigger>
|
||||||
<el-icon class="mr-2 h-4 w-4" />
|
<span class="mr-2 h-4 w-4" />
|
||||||
<span>{{ props.title }}</span>
|
<span>{{ props.title }}</span>
|
||||||
</MenubarSubTrigger>
|
</MenubarSubTrigger>
|
||||||
<MenubarSubContent class="max-h-56 overflow-auto">
|
<MenubarSubContent class="max-h-56 overflow-auto">
|
||||||
<MenubarItem
|
<MenubarCheckboxItem
|
||||||
v-for="{ label, value, desc } in options"
|
v-for="{ label, value, desc } in options"
|
||||||
:key="value"
|
:key="value"
|
||||||
:label="label"
|
:label="label"
|
||||||
:model-value="value"
|
:model-value="value"
|
||||||
class="w-50"
|
class="w-50"
|
||||||
|
:checked="current === value"
|
||||||
@click="change(value)"
|
@click="change(value)"
|
||||||
>
|
>
|
||||||
<el-icon class="mr-2 h-4 w-4" :style="{ opacity: +(current === value) }">
|
|
||||||
<ElIconCheck />
|
|
||||||
</el-icon>
|
|
||||||
{{ label }}
|
{{ label }}
|
||||||
<DropdownMenuShortcut :style="setStyle(title, value)">
|
<DropdownMenuShortcut :style="setStyle(title, value)">
|
||||||
{{ desc }}
|
{{ desc }}
|
||||||
</DropdownMenuShortcut>
|
</DropdownMenuShortcut>
|
||||||
</MenubarItem>
|
</MenubarCheckboxItem>
|
||||||
</MenubarSubContent>
|
</MenubarSubContent>
|
||||||
</MenubarSub>
|
</MenubarSub>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { Format } from 'vue-pick-colors'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import {
|
import {
|
||||||
Menubar,
|
Menubar,
|
||||||
|
MenubarCheckboxItem,
|
||||||
MenubarContent,
|
MenubarContent,
|
||||||
MenubarItem,
|
|
||||||
MenubarMenu,
|
MenubarMenu,
|
||||||
MenubarSeparator,
|
MenubarSeparator,
|
||||||
MenubarShortcut,
|
MenubarShortcut,
|
||||||
@ -17,7 +18,7 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select'
|
} from '@/components/ui/select'
|
||||||
|
import { Toaster } from '@/components/ui/sonner'
|
||||||
import {
|
import {
|
||||||
altSign,
|
altSign,
|
||||||
codeBlockThemeOptions,
|
codeBlockThemeOptions,
|
||||||
@ -32,16 +33,15 @@ import {
|
|||||||
} from '@/config'
|
} from '@/config'
|
||||||
import { useDisplayStore, useStore } from '@/stores'
|
import { useDisplayStore, useStore } from '@/stores'
|
||||||
import { mergeCss, solveWeChatImage } from '@/utils'
|
import { mergeCss, solveWeChatImage } from '@/utils'
|
||||||
import { ElNotification } from 'element-plus'
|
|
||||||
import { Moon, Paintbrush, Sun } from 'lucide-vue-next'
|
import { Moon, Paintbrush, Sun } from 'lucide-vue-next'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { nextTick, ref, useTemplateRef } from 'vue'
|
||||||
|
import PickColors from 'vue-pick-colors'
|
||||||
|
import { toast } from 'vue-sonner'
|
||||||
|
|
||||||
import { nextTick } from 'vue'
|
|
||||||
import EditDropdown from './EditDropdown.vue'
|
import EditDropdown from './EditDropdown.vue'
|
||||||
|
|
||||||
import FileDropdown from './FileDropdown.vue'
|
import FileDropdown from './FileDropdown.vue'
|
||||||
import HelpDropdown from './HelpDropdown.vue'
|
import HelpDropdown from './HelpDropdown.vue'
|
||||||
|
|
||||||
import PostInfo from './PostInfo.vue'
|
import PostInfo from './PostInfo.vue'
|
||||||
import StyleDropdown from './StyleDropdown.vue'
|
import StyleDropdown from './StyleDropdown.vue'
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ function copy() {
|
|||||||
// 公众号不支持 position, 转换为等价的 translateY
|
// 公众号不支持 position, 转换为等价的 translateY
|
||||||
.replace(/top:(.*?)em/g, `transform: translateY($1em)`)
|
.replace(/top:(.*?)em/g, `transform: translateY($1em)`)
|
||||||
// 适配主题中的颜色变量
|
// 适配主题中的颜色变量
|
||||||
.replaceAll(`var(--el-text-color-regular)`, `#3f3f3f`)
|
.replaceAll(`hsl(var(--foreground))`, `#3f3f3f`)
|
||||||
.replaceAll(`var(--blockquote-background)`, `#f7f7f7`)
|
.replaceAll(`var(--blockquote-background)`, `#f7f7f7`)
|
||||||
.replaceAll(`var(--md-primary-color)`, primaryColor.value)
|
.replaceAll(`var(--md-primary-color)`, primaryColor.value)
|
||||||
.replaceAll(/--md-primary-color:.+?;/g, ``)
|
.replaceAll(/--md-primary-color:.+?;/g, ``)
|
||||||
@ -169,13 +169,7 @@ function copy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 输出提示
|
// 输出提示
|
||||||
ElNotification({
|
toast.success(`已复制渲染后的文章到剪贴板,可直接到公众号后台粘贴`)
|
||||||
showClose: true,
|
|
||||||
message: `已复制渲染后的文章到剪贴板,可直接到公众号后台粘贴`,
|
|
||||||
offset: 80,
|
|
||||||
duration: 1600,
|
|
||||||
type: `success`,
|
|
||||||
})
|
|
||||||
|
|
||||||
editorRefresh()
|
editorRefresh()
|
||||||
emit(`endCopy`)
|
emit(`endCopy`)
|
||||||
@ -189,6 +183,10 @@ function customStyle() {
|
|||||||
store.cssEditor!.refresh()
|
store.cssEditor!.refresh()
|
||||||
}, 50)
|
}, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pickColorsContainer = useTemplateRef(`pickColorsContainer`)
|
||||||
|
const format = ref<Format>(`rgb`)
|
||||||
|
const formatOptions = ref<Format[]>([`rgb`, `hex`, `hsl`, `hsv`])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -199,25 +197,21 @@ function customStyle() {
|
|||||||
<MenubarMenu>
|
<MenubarMenu>
|
||||||
<MenubarTrigger> 格式 </MenubarTrigger>
|
<MenubarTrigger> 格式 </MenubarTrigger>
|
||||||
<MenubarContent class="w-60" align="start">
|
<MenubarContent class="w-60" align="start">
|
||||||
<MenubarItem
|
<MenubarCheckboxItem
|
||||||
v-for="{ label, kbd, emitArgs } in formatItems" :key="label"
|
v-for="{ label, kbd, emitArgs } in formatItems" :key="label"
|
||||||
@click="emitArgs[0] === 'addFormat' ? $emit(emitArgs[0], emitArgs[1]) : $emit(emitArgs[0])"
|
@click="emitArgs[0] === 'addFormat' ? $emit(emitArgs[0], emitArgs[1]) : $emit(emitArgs[0])"
|
||||||
>
|
>
|
||||||
<el-icon class="mr-2 h-4 w-4" />
|
|
||||||
{{ label }}
|
{{ label }}
|
||||||
<MenubarShortcut>
|
<MenubarShortcut>
|
||||||
<kbd v-for="item in kbd" :key="item" class="mx-1 bg-gray-2 dark:bg-stone-9">
|
<kbd v-for="item in kbd" :key="item" class="mx-1 bg-gray-2 dark:bg-stone-9">
|
||||||
{{ item }}
|
{{ item }}
|
||||||
</kbd>
|
</kbd>
|
||||||
</MenubarShortcut>
|
</MenubarShortcut>
|
||||||
</MenubarItem>
|
</MenubarCheckboxItem>
|
||||||
<MenubarSeparator />
|
<MenubarSeparator />
|
||||||
<MenubarItem @click="citeStatusChanged()">
|
<MenubarCheckboxItem :checked="isCiteStatus" @click="citeStatusChanged()">
|
||||||
<el-icon class="mr-2 h-4 w-4" :class="{ 'opacity-0': !isCiteStatus }">
|
|
||||||
<ElIconCheck />
|
|
||||||
</el-icon>
|
|
||||||
微信外链转底部引用
|
微信外链转底部引用
|
||||||
</MenubarItem>
|
</MenubarCheckboxItem>
|
||||||
</MenubarContent>
|
</MenubarContent>
|
||||||
</MenubarMenu>
|
</MenubarMenu>
|
||||||
<EditDropdown />
|
<EditDropdown />
|
||||||
@ -288,8 +282,15 @@ function customStyle() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<h2>自定义主题色</h2>
|
<h2>自定义主题色</h2>
|
||||||
<div>
|
<div ref="pickColorsContainer">
|
||||||
<el-color-picker v-model="primaryColor" :teleported="false" show-alpha @change="store.colorChanged" />
|
<PickColors
|
||||||
|
v-model:value="primaryColor"
|
||||||
|
show-alpha
|
||||||
|
:format="format" :format-options="formatOptions"
|
||||||
|
:theme="store.isDark ? 'dark' : 'light'"
|
||||||
|
:popup-container="pickColorsContainer!"
|
||||||
|
@change="store.colorChanged"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
@ -436,13 +437,11 @@ function customStyle() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<h2>样式配置</h2>
|
<h2>样式配置</h2>
|
||||||
<div>
|
<Button @click="store.resetStyleConfirm">
|
||||||
<Button class="w-full" @click="store.resetStyleConfirm()">
|
|
||||||
重置
|
重置
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
<Button variant="outline" class="mx-2" @click="copy">
|
<Button variant="outline" class="mx-2" @click="copy">
|
||||||
@ -450,6 +449,8 @@ function customStyle() {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<PostInfo />
|
<PostInfo />
|
||||||
|
|
||||||
|
<Toaster rich-colors position="top-center" />
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -6,6 +6,14 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog'
|
} from '@/components/ui/dialog'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import {
|
||||||
|
NumberField,
|
||||||
|
NumberFieldContent,
|
||||||
|
NumberFieldDecrement,
|
||||||
|
NumberFieldIncrement,
|
||||||
|
NumberFieldInput,
|
||||||
|
} from '@/components/ui/number-field'
|
||||||
import { useDisplayStore, useStore } from '@/stores'
|
import { useDisplayStore, useStore } from '@/stores'
|
||||||
|
|
||||||
import { createTable } from '@/utils'
|
import { createTable } from '@/utils'
|
||||||
@ -51,40 +59,36 @@ function onUpdate(val: boolean) {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>插入表格</DialogTitle>
|
<DialogTitle>插入表格</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<el-row class="tb-options" type="flex" align="middle" :gutter="10">
|
<div class="space-x-2 flex justify-between">
|
||||||
<el-col :span="12">
|
<NumberField v-model="rowNum" :min="1" :max="100">
|
||||||
行数:
|
<Label>行数</Label>
|
||||||
<el-input-number
|
<NumberFieldContent>
|
||||||
v-model="rowNum"
|
<NumberFieldDecrement />
|
||||||
controls-position="right"
|
<NumberFieldInput />
|
||||||
:min="1"
|
<NumberFieldIncrement />
|
||||||
:max="100"
|
</NumberFieldContent>
|
||||||
size="small"
|
</NumberField>
|
||||||
/>
|
<NumberField v-model="colNum" :min="1" :max="100">
|
||||||
</el-col>
|
<Label>列数</Label>
|
||||||
<el-col :span="12">
|
<NumberFieldContent>
|
||||||
列数:
|
<NumberFieldDecrement />
|
||||||
<el-input-number
|
<NumberFieldInput />
|
||||||
v-model="colNum"
|
<NumberFieldIncrement />
|
||||||
controls-position="right"
|
</NumberFieldContent>
|
||||||
:min="1"
|
</NumberField>
|
||||||
:max="100"
|
</div>
|
||||||
size="small"
|
<div class="space-y-2 border-1 rounded p-2">
|
||||||
/>
|
<div v-for="row in rowNum + 1" :key="row" :class="{ 'head-style': row === 1 }" class="space-x-2 flex">
|
||||||
</el-col>
|
<Input
|
||||||
</el-row>
|
v-for="col in colNum" :key="col"
|
||||||
<table style="border-collapse: collapse" class="input-table">
|
|
||||||
<tr v-for="row in rowNum + 1" :key="row" :class="{ 'head-style': row === 1 }">
|
|
||||||
<td v-for="col in colNum" :key="col">
|
|
||||||
<el-input
|
|
||||||
v-model="tableData[`k_${row - 1}_${col - 1}`]"
|
v-model="tableData[`k_${row - 1}_${col - 1}`]"
|
||||||
align="center"
|
:class="{
|
||||||
|
'bg-gray-100 dark:bg-gray-900': row === 1,
|
||||||
|
}"
|
||||||
:placeholder="row === 1 ? '表头' : ''"
|
:placeholder="row === 1 ? '表头' : ''"
|
||||||
/>
|
/>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" @click="toggleShowInsertFormDialog(false)">
|
<Button variant="outline" @click="toggleShowInsertFormDialog(false)">
|
||||||
取 消
|
取 消
|
||||||
@ -98,11 +102,4 @@ function onUpdate(val: boolean) {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.tb-options {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-table :deep(.el-input__inner) {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
File diff suppressed because it is too large
Load Diff
70
src/components/CustomUploadForm.vue
Normal file
70
src/components/CustomUploadForm.vue
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<script setup lang='ts'>
|
||||||
|
import { useStore } from '@/stores'
|
||||||
|
import { removeLeft } from '@/utils'
|
||||||
|
import { useLocalStorage } from '@vueuse/core'
|
||||||
|
import CodeMirror from 'codemirror'
|
||||||
|
import { markRaw, nextTick, onMounted, ref, useTemplateRef } from 'vue'
|
||||||
|
import { toast } from 'vue-sonner'
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
|
const code = useLocalStorage(`formCustomConfig`, removeLeft(`
|
||||||
|
const {file, util, okCb, errCb} = CUSTOM_ARG
|
||||||
|
const param = new FormData()
|
||||||
|
param.append('file', file)
|
||||||
|
util.axios.post('${window.location.origin}/upload', param, {
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
}).then(res => {
|
||||||
|
okCb(res.url)
|
||||||
|
}).catch(err => {
|
||||||
|
errCb(err)
|
||||||
|
})
|
||||||
|
`).trim())
|
||||||
|
|
||||||
|
const formCustomTextarea = useTemplateRef<HTMLTextAreaElement>(`formCustomTextarea`)
|
||||||
|
|
||||||
|
const editor = ref< CodeMirror.EditorFromTextArea | null>(null)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
editor.value = markRaw(CodeMirror.fromTextArea(formCustomTextarea.value!, {
|
||||||
|
mode: `javascript`,
|
||||||
|
theme: store.isDark ? `darcula` : `xq-light`,
|
||||||
|
lineNumbers: true,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// 嵌套使用 nextTick 才能确保生效,具体原因未知
|
||||||
|
nextTick(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
editor.value?.setValue(code.value)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function formCustomSave() {
|
||||||
|
const str = editor.value!.getValue()
|
||||||
|
localStorage.setItem(`formCustomConfig`, str)
|
||||||
|
toast.success(`保存成功`)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="h-60 border">
|
||||||
|
<textarea
|
||||||
|
ref="formCustomTextarea"
|
||||||
|
placeholder="Your custom code here."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
class="p-0"
|
||||||
|
href="https://github.com/doocs/md#自定义上传逻辑"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
参数详情
|
||||||
|
</Button>
|
||||||
|
<Button class="block" @click="formCustomSave">
|
||||||
|
保存配置
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
27
src/components/FormItem.vue
Normal file
27
src/components/FormItem.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
label?: string
|
||||||
|
required?: boolean
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Label class="flex items-center">
|
||||||
|
<span class="mr-4 w-[150px] text-right font-bold min-h-4 flex-shrink-0" :class="{ required: props.required }">
|
||||||
|
{{ props.label }}
|
||||||
|
</span>
|
||||||
|
<slot />
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang='less'>
|
||||||
|
.required::before {
|
||||||
|
content: '*';
|
||||||
|
color: #f00;
|
||||||
|
margin-right: 0.25em;
|
||||||
|
}
|
||||||
|
</style>
|
@ -34,7 +34,7 @@ onMounted(() => {
|
|||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
background-color: var(--el-bg-color-page);
|
background-color: hsl(var(--background));
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: url('../assets/images/favicon.png');
|
content: url('../assets/images/favicon.png');
|
||||||
|
14
src/components/ui/alert-dialog/AlertDialog.vue
Normal file
14
src/components/ui/alert-dialog/AlertDialog.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { type AlertDialogEmits, type AlertDialogProps, AlertDialogRoot, useForwardPropsEmits } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<AlertDialogProps>()
|
||||||
|
const emits = defineEmits<AlertDialogEmits>()
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogRoot v-bind="forwarded">
|
||||||
|
<slot />
|
||||||
|
</AlertDialogRoot>
|
||||||
|
</template>
|
20
src/components/ui/alert-dialog/AlertDialogAction.vue
Normal file
20
src/components/ui/alert-dialog/AlertDialogAction.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { buttonVariants } from '@/components/ui/button'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { AlertDialogAction, type AlertDialogActionProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<AlertDialogActionProps & { class?: HTMLAttributes[`class`] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogAction v-bind="delegatedProps" :class="cn(buttonVariants(), props.class)">
|
||||||
|
<slot />
|
||||||
|
</AlertDialogAction>
|
||||||
|
</template>
|
27
src/components/ui/alert-dialog/AlertDialogCancel.vue
Normal file
27
src/components/ui/alert-dialog/AlertDialogCancel.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { buttonVariants } from '@/components/ui/button'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { AlertDialogCancel, type AlertDialogCancelProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<AlertDialogCancelProps & { class?: HTMLAttributes[`class`] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogCancel
|
||||||
|
v-bind="delegatedProps"
|
||||||
|
:class="cn(
|
||||||
|
buttonVariants({ variant: 'outline' }),
|
||||||
|
'mt-2 sm:mt-0',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</AlertDialogCancel>
|
||||||
|
</template>
|
42
src/components/ui/alert-dialog/AlertDialogContent.vue
Normal file
42
src/components/ui/alert-dialog/AlertDialogContent.vue
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import {
|
||||||
|
AlertDialogContent,
|
||||||
|
type AlertDialogContentEmits,
|
||||||
|
type AlertDialogContentProps,
|
||||||
|
AlertDialogOverlay,
|
||||||
|
AlertDialogPortal,
|
||||||
|
useForwardPropsEmits,
|
||||||
|
} from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<AlertDialogContentProps & { class?: HTMLAttributes[`class`] }>()
|
||||||
|
const emits = defineEmits<AlertDialogContentEmits>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogPortal>
|
||||||
|
<AlertDialogOverlay
|
||||||
|
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80"
|
||||||
|
/>
|
||||||
|
<AlertDialogContent
|
||||||
|
v-bind="forwarded"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialogPortal>
|
||||||
|
</template>
|
25
src/components/ui/alert-dialog/AlertDialogDescription.vue
Normal file
25
src/components/ui/alert-dialog/AlertDialogDescription.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import {
|
||||||
|
AlertDialogDescription,
|
||||||
|
type AlertDialogDescriptionProps,
|
||||||
|
} from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<AlertDialogDescriptionProps & { class?: HTMLAttributes[`class`] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogDescription
|
||||||
|
v-bind="delegatedProps"
|
||||||
|
:class="cn('text-sm text-muted-foreground', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</template>
|
21
src/components/ui/alert-dialog/AlertDialogFooter.vue
Normal file
21
src/components/ui/alert-dialog/AlertDialogFooter.vue
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes[`class`]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-x-2',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
16
src/components/ui/alert-dialog/AlertDialogHeader.vue
Normal file
16
src/components/ui/alert-dialog/AlertDialogHeader.vue
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes[`class`]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="cn('flex flex-col gap-y-2 text-center sm:text-left', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
22
src/components/ui/alert-dialog/AlertDialogTitle.vue
Normal file
22
src/components/ui/alert-dialog/AlertDialogTitle.vue
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { AlertDialogTitle, type AlertDialogTitleProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<AlertDialogTitleProps & { class?: HTMLAttributes[`class`] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogTitle
|
||||||
|
v-bind="delegatedProps"
|
||||||
|
:class="cn('text-lg font-semibold', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</AlertDialogTitle>
|
||||||
|
</template>
|
11
src/components/ui/alert-dialog/AlertDialogTrigger.vue
Normal file
11
src/components/ui/alert-dialog/AlertDialogTrigger.vue
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { AlertDialogTrigger, type AlertDialogTriggerProps } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<AlertDialogTriggerProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AlertDialogTrigger v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
</template>
|
9
src/components/ui/alert-dialog/index.ts
Normal file
9
src/components/ui/alert-dialog/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export { default as AlertDialog } from './AlertDialog.vue'
|
||||||
|
export { default as AlertDialogAction } from './AlertDialogAction.vue'
|
||||||
|
export { default as AlertDialogCancel } from './AlertDialogCancel.vue'
|
||||||
|
export { default as AlertDialogContent } from './AlertDialogContent.vue'
|
||||||
|
export { default as AlertDialogDescription } from './AlertDialogDescription.vue'
|
||||||
|
export { default as AlertDialogFooter } from './AlertDialogFooter.vue'
|
||||||
|
export { default as AlertDialogHeader } from './AlertDialogHeader.vue'
|
||||||
|
export { default as AlertDialogTitle } from './AlertDialogTitle.vue'
|
||||||
|
export { default as AlertDialogTrigger } from './AlertDialogTrigger.vue'
|
16
src/components/ui/alert/Alert.vue
Normal file
16
src/components/ui/alert/Alert.vue
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { type AlertVariants, alertVariants } from '.'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
variant?: AlertVariants['variant']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="cn(alertVariants({ variant }), props.class)" role="alert">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
14
src/components/ui/alert/AlertDescription.vue
Normal file
14
src/components/ui/alert/AlertDescription.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="cn('text-sm [&_p]:leading-relaxed', props.class)">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
14
src/components/ui/alert/AlertTitle.vue
Normal file
14
src/components/ui/alert/AlertTitle.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h5 :class="cn('mb-1 font-medium leading-none tracking-tight', props.class)">
|
||||||
|
<slot />
|
||||||
|
</h5>
|
||||||
|
</template>
|
23
src/components/ui/alert/index.ts
Normal file
23
src/components/ui/alert/index.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
|
export { default as Alert } from './Alert.vue'
|
||||||
|
export { default as AlertDescription } from './AlertDescription.vue'
|
||||||
|
export { default as AlertTitle } from './AlertTitle.vue'
|
||||||
|
|
||||||
|
export const alertVariants = cva(
|
||||||
|
'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: 'bg-background text-foreground',
|
||||||
|
destructive:
|
||||||
|
'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'default',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export type AlertVariants = VariantProps<typeof alertVariants>
|
24
src/components/ui/input/Input.vue
Normal file
24
src/components/ui/input/Input.vue
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
defaultValue?: string | number
|
||||||
|
modelValue?: string | number
|
||||||
|
class?: HTMLAttributes[`class`]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emits = defineEmits<{
|
||||||
|
(e: `update:modelValue`, payload: string | number): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const modelValue = useVModel(props, `modelValue`, emits, {
|
||||||
|
passive: true,
|
||||||
|
defaultValue: props.defaultValue,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<input v-model="modelValue" :class="cn('flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', props.class)">
|
||||||
|
</template>
|
1
src/components/ui/input/index.ts
Normal file
1
src/components/ui/input/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as Input } from './Input.vue'
|
27
src/components/ui/label/Label.vue
Normal file
27
src/components/ui/label/Label.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Label, type LabelProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<LabelProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Label
|
||||||
|
v-bind="delegatedProps"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</Label>
|
||||||
|
</template>
|
1
src/components/ui/label/index.ts
Normal file
1
src/components/ui/label/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as Label } from './Label.vue'
|
23
src/components/ui/number-field/NumberField.vue
Normal file
23
src/components/ui/number-field/NumberField.vue
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { NumberFieldRootEmits, NumberFieldRootProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { NumberFieldRoot, useForwardPropsEmits } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<NumberFieldRootProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
const emits = defineEmits<NumberFieldRootEmits>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NumberFieldRoot v-bind="forwarded" :class="cn('grid gap-1.5', props.class)">
|
||||||
|
<slot />
|
||||||
|
</NumberFieldRoot>
|
||||||
|
</template>
|
14
src/components/ui/number-field/NumberFieldContent.vue
Normal file
14
src/components/ui/number-field/NumberFieldContent.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="cn('relative [&>[data-slot=input]]:has-[[data-slot=increment]]:pr-5 [&>[data-slot=input]]:has-[[data-slot=decrement]]:pl-5', props.class)">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
25
src/components/ui/number-field/NumberFieldDecrement.vue
Normal file
25
src/components/ui/number-field/NumberFieldDecrement.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { NumberFieldDecrementProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Minus } from 'lucide-vue-next'
|
||||||
|
import { NumberFieldDecrement, useForwardProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<NumberFieldDecrementProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwarded = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NumberFieldDecrement data-slot="decrement" v-bind="forwarded" :class="cn('absolute top-1/2 -translate-y-1/2 left-0 p-3 disabled:cursor-not-allowed disabled:opacity-20', props.class)">
|
||||||
|
<slot>
|
||||||
|
<Minus class="h-4 w-4" />
|
||||||
|
</slot>
|
||||||
|
</NumberFieldDecrement>
|
||||||
|
</template>
|
25
src/components/ui/number-field/NumberFieldIncrement.vue
Normal file
25
src/components/ui/number-field/NumberFieldIncrement.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { NumberFieldIncrementProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Plus } from 'lucide-vue-next'
|
||||||
|
import { NumberFieldIncrement, useForwardProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<NumberFieldIncrementProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwarded = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NumberFieldIncrement data-slot="increment" v-bind="forwarded" :class="cn('absolute top-1/2 -translate-y-1/2 right-0 disabled:cursor-not-allowed disabled:opacity-20 p-3', props.class)">
|
||||||
|
<slot>
|
||||||
|
<Plus class="h-4 w-4" />
|
||||||
|
</slot>
|
||||||
|
</NumberFieldIncrement>
|
||||||
|
</template>
|
16
src/components/ui/number-field/NumberFieldInput.vue
Normal file
16
src/components/ui/number-field/NumberFieldInput.vue
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { NumberFieldInput } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NumberFieldInput
|
||||||
|
data-slot="input"
|
||||||
|
:class="cn('flex h-10 w-full rounded-md border border-input bg-background py-2 text-sm text-center ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', props.class)"
|
||||||
|
/>
|
||||||
|
</template>
|
5
src/components/ui/number-field/index.ts
Normal file
5
src/components/ui/number-field/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export { default as NumberField } from './NumberField.vue'
|
||||||
|
export { default as NumberFieldContent } from './NumberFieldContent.vue'
|
||||||
|
export { default as NumberFieldDecrement } from './NumberFieldDecrement.vue'
|
||||||
|
export { default as NumberFieldIncrement } from './NumberFieldIncrement.vue'
|
||||||
|
export { default as NumberFieldInput } from './NumberFieldInput.vue'
|
22
src/components/ui/sonner/Sonner.vue
Normal file
22
src/components/ui/sonner/Sonner.vue
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { Toaster as Sonner, type ToasterProps } from 'vue-sonner'
|
||||||
|
|
||||||
|
const props = defineProps<ToasterProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Sonner
|
||||||
|
class="toaster group"
|
||||||
|
v-bind="props"
|
||||||
|
:toast-options="{
|
||||||
|
classes: {
|
||||||
|
toast: 'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
|
||||||
|
description: 'group-[.toast]:text-muted-foreground',
|
||||||
|
actionButton:
|
||||||
|
'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
|
||||||
|
cancelButton:
|
||||||
|
'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</template>
|
1
src/components/ui/sonner/index.ts
Normal file
1
src/components/ui/sonner/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as Toaster } from './Sonner.vue'
|
39
src/components/ui/switch/Switch.vue
Normal file
39
src/components/ui/switch/Switch.vue
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import {
|
||||||
|
SwitchRoot,
|
||||||
|
type SwitchRootEmits,
|
||||||
|
type SwitchRootProps,
|
||||||
|
SwitchThumb,
|
||||||
|
useForwardPropsEmits,
|
||||||
|
} from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<SwitchRootProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const emits = defineEmits<SwitchRootEmits>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SwitchRoot
|
||||||
|
v-bind="forwarded"
|
||||||
|
:class="cn(
|
||||||
|
'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<SwitchThumb
|
||||||
|
:class="cn('pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5')"
|
||||||
|
>
|
||||||
|
<slot name="thumb" />
|
||||||
|
</SwitchThumb>
|
||||||
|
</SwitchRoot>
|
||||||
|
</template>
|
1
src/components/ui/switch/index.ts
Normal file
1
src/components/ui/switch/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as Switch } from './Switch.vue'
|
15
src/components/ui/tabs/Tabs.vue
Normal file
15
src/components/ui/tabs/Tabs.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { TabsRootEmits, TabsRootProps } from 'radix-vue'
|
||||||
|
import { TabsRoot, useForwardPropsEmits } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<TabsRootProps>()
|
||||||
|
const emits = defineEmits<TabsRootEmits>()
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<TabsRoot v-bind="forwarded">
|
||||||
|
<slot />
|
||||||
|
</TabsRoot>
|
||||||
|
</template>
|
22
src/components/ui/tabs/TabsContent.vue
Normal file
22
src/components/ui/tabs/TabsContent.vue
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { TabsContent, type TabsContentProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<TabsContentProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<TabsContent
|
||||||
|
:class="cn('mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', props.class)"
|
||||||
|
v-bind="delegatedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</TabsContent>
|
||||||
|
</template>
|
25
src/components/ui/tabs/TabsList.vue
Normal file
25
src/components/ui/tabs/TabsList.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { TabsList, type TabsListProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<TabsListProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<TabsList
|
||||||
|
v-bind="delegatedProps"
|
||||||
|
:class="cn(
|
||||||
|
'inline-flex items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</TabsList>
|
||||||
|
</template>
|
29
src/components/ui/tabs/TabsTrigger.vue
Normal file
29
src/components/ui/tabs/TabsTrigger.vue
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { TabsTrigger, type TabsTriggerProps, useForwardProps } from 'radix-vue'
|
||||||
|
import { computed, type HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<TabsTriggerProps & { class?: HTMLAttributes['class'] }>()
|
||||||
|
|
||||||
|
const delegatedProps = computed(() => {
|
||||||
|
const { class: _, ...delegated } = props
|
||||||
|
|
||||||
|
return delegated
|
||||||
|
})
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<TabsTrigger
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
:class="cn(
|
||||||
|
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<span class="truncate">
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
</TabsTrigger>
|
||||||
|
</template>
|
4
src/components/ui/tabs/index.ts
Normal file
4
src/components/ui/tabs/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export { default as Tabs } from './Tabs.vue'
|
||||||
|
export { default as TabsContent } from './TabsContent.vue'
|
||||||
|
export { default as TabsList } from './TabsList.vue'
|
||||||
|
export { default as TabsTrigger } from './TabsTrigger.vue'
|
24
src/components/ui/textarea/Textarea.vue
Normal file
24
src/components/ui/textarea/Textarea.vue
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
defaultValue?: string | number
|
||||||
|
modelValue?: string | number
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emits = defineEmits<{
|
||||||
|
(e: 'update:modelValue', payload: string | number): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const modelValue = useVModel(props, 'modelValue', emits, {
|
||||||
|
passive: true,
|
||||||
|
defaultValue: props.defaultValue,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<textarea v-model="modelValue" :class="cn('flex min-h-20 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', props.class)" />
|
||||||
|
</template>
|
1
src/components/ui/textarea/index.ts
Normal file
1
src/components/ui/textarea/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as Textarea } from './Textarea.vue'
|
@ -15,7 +15,7 @@ const defaultTheme: Theme = {
|
|||||||
'padding': `0 1em`,
|
'padding': `0 1em`,
|
||||||
'border-bottom': `2px solid var(--md-primary-color)`,
|
'border-bottom': `2px solid var(--md-primary-color)`,
|
||||||
'margin': `2em auto 1em`,
|
'margin': `2em auto 1em`,
|
||||||
'color': `var(--el-text-color-regular)`,
|
'color': `hsl(var(--foreground))`,
|
||||||
'font-size': `1.2em`,
|
'font-size': `1.2em`,
|
||||||
'font-weight': `bold`,
|
'font-weight': `bold`,
|
||||||
'text-align': `center`,
|
'text-align': `center`,
|
||||||
@ -38,7 +38,7 @@ const defaultTheme: Theme = {
|
|||||||
'padding-left': `8px`,
|
'padding-left': `8px`,
|
||||||
'border-left': `3px solid var(--md-primary-color)`,
|
'border-left': `3px solid var(--md-primary-color)`,
|
||||||
'margin': `2em 8px 0.75em 0`,
|
'margin': `2em 8px 0.75em 0`,
|
||||||
'color': `var(--el-text-color-regular)`,
|
'color': `hsl(var(--foreground))`,
|
||||||
'font-size': `1.1em`,
|
'font-size': `1.1em`,
|
||||||
'font-weight': `bold`,
|
'font-weight': `bold`,
|
||||||
'line-height': `1.2`,
|
'line-height': `1.2`,
|
||||||
@ -71,7 +71,7 @@ const defaultTheme: Theme = {
|
|||||||
p: {
|
p: {
|
||||||
'margin': `1.5em 8px`,
|
'margin': `1.5em 8px`,
|
||||||
'letter-spacing': `0.1em`,
|
'letter-spacing': `0.1em`,
|
||||||
'color': `var(--el-text-color-regular)`,
|
'color': `hsl(var(--foreground))`,
|
||||||
'text-align': `justify`,
|
'text-align': `justify`,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ const defaultTheme: Theme = {
|
|||||||
'display': `block`,
|
'display': `block`,
|
||||||
'font-size': `1em`,
|
'font-size': `1em`,
|
||||||
'letter-spacing': `0.1em`,
|
'letter-spacing': `0.1em`,
|
||||||
'color': `var(--el-text-color-regular)`,
|
'color': `hsl(var(--foreground))`,
|
||||||
},
|
},
|
||||||
|
|
||||||
blockquote_note: {
|
blockquote_note: {
|
||||||
@ -181,7 +181,7 @@ const defaultTheme: Theme = {
|
|||||||
ol: {
|
ol: {
|
||||||
'padding-left': `1em`,
|
'padding-left': `1em`,
|
||||||
'margin-left': `0`,
|
'margin-left': `0`,
|
||||||
'color': `var(--el-text-color-regular)`,
|
'color': `hsl(var(--foreground))`,
|
||||||
},
|
},
|
||||||
|
|
||||||
// 无序列表
|
// 无序列表
|
||||||
@ -189,18 +189,18 @@ const defaultTheme: Theme = {
|
|||||||
'list-style': `circle`,
|
'list-style': `circle`,
|
||||||
'padding-left': `1em`,
|
'padding-left': `1em`,
|
||||||
'margin-left': `0`,
|
'margin-left': `0`,
|
||||||
'color': `var(--el-text-color-regular)`,
|
'color': `hsl(var(--foreground))`,
|
||||||
},
|
},
|
||||||
|
|
||||||
footnotes: {
|
footnotes: {
|
||||||
'margin': `0.5em 8px`,
|
'margin': `0.5em 8px`,
|
||||||
'font-size': `80%`,
|
'font-size': `80%`,
|
||||||
'color': `var(--el-text-color-regular)`,
|
'color': `hsl(var(--foreground))`,
|
||||||
},
|
},
|
||||||
|
|
||||||
figure: {
|
figure: {
|
||||||
margin: `1.5em 8px`,
|
margin: `1.5em 8px`,
|
||||||
color: `var(--el-text-color-regular)`,
|
color: `hsl(var(--foreground))`,
|
||||||
},
|
},
|
||||||
|
|
||||||
hr: {
|
hr: {
|
||||||
@ -218,7 +218,7 @@ const defaultTheme: Theme = {
|
|||||||
'text-indent': `-1em`,
|
'text-indent': `-1em`,
|
||||||
'display': `block`,
|
'display': `block`,
|
||||||
'margin': `0.2em 8px`,
|
'margin': `0.2em 8px`,
|
||||||
'color': `var(--el-text-color-regular)`,
|
'color': `hsl(var(--foreground))`,
|
||||||
},
|
},
|
||||||
|
|
||||||
codespan: {
|
codespan: {
|
||||||
@ -255,13 +255,13 @@ const defaultTheme: Theme = {
|
|||||||
'border-collapse': `collapse`,
|
'border-collapse': `collapse`,
|
||||||
'text-align': `center`,
|
'text-align': `center`,
|
||||||
'margin': `1em 8px`,
|
'margin': `1em 8px`,
|
||||||
'color': `var(--el-text-color-regular)`,
|
'color': `hsl(var(--foreground))`,
|
||||||
},
|
},
|
||||||
|
|
||||||
thead: {
|
thead: {
|
||||||
'background': `rgba(0, 0, 0, 0.05)`,
|
'background': `rgba(0, 0, 0, 0.05)`,
|
||||||
'font-weight': `bold`,
|
'font-weight': `bold`,
|
||||||
'color': `var(--el-text-color-regular)`,
|
'color': `hsl(var(--foreground))`,
|
||||||
},
|
},
|
||||||
|
|
||||||
td: {
|
td: {
|
||||||
@ -273,7 +273,7 @@ const defaultTheme: Theme = {
|
|||||||
|
|
||||||
footnote: {
|
footnote: {
|
||||||
'font-size': `12px`,
|
'font-size': `12px`,
|
||||||
'color': `var(--el-text-color-regular)`,
|
'color': `hsl(var(--foreground))`,
|
||||||
},
|
},
|
||||||
|
|
||||||
figcaption: {
|
figcaption: {
|
||||||
@ -403,7 +403,7 @@ const graceTheme = toMerged(defaultTheme, {
|
|||||||
'border-spacing': `0`,
|
'border-spacing': `0`,
|
||||||
'border-radius': `8px`,
|
'border-radius': `8px`,
|
||||||
'margin': `1em 8px`,
|
'margin': `1em 8px`,
|
||||||
'color': `var(--el-text-color-regular)`,
|
'color': `hsl(var(--foreground))`,
|
||||||
'box-shadow': `0 4px 6px rgba(0,0,0,0.1)`,
|
'box-shadow': `0 4px 6px rgba(0,0,0,0.1)`,
|
||||||
'overflow': `hidden`,
|
'overflow': `hidden`,
|
||||||
},
|
},
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
import type { App } from 'vue'
|
|
||||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
|
||||||
import { ElLoading, ElMessage } from 'element-plus'
|
|
||||||
import 'element-plus/dist/index.css'
|
|
||||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
install(app: App<Element>) {
|
|
||||||
// app.use(ElementPlus, { size: `default` })
|
|
||||||
|
|
||||||
app.config.globalProperties.$loading = ElLoading.service
|
|
||||||
app.config.globalProperties.$message = ElMessage
|
|
||||||
|
|
||||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
|
||||||
app.component(`ElIcon${key}`, component)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import ElementPlus from './element'
|
|
||||||
|
|
||||||
import 'virtual:uno.css'
|
import 'virtual:uno.css'
|
||||||
import 'codemirror/lib/codemirror.css'
|
import 'codemirror/lib/codemirror.css'
|
||||||
@ -13,6 +12,7 @@ import '@/assets/index.css'
|
|||||||
import '@/assets/less/theme.less'
|
import '@/assets/less/theme.less'
|
||||||
|
|
||||||
import 'codemirror/mode/css/css'
|
import 'codemirror/mode/css/css'
|
||||||
|
import 'codemirror/mode/javascript/javascript'
|
||||||
import 'codemirror/mode/markdown/markdown'
|
import 'codemirror/mode/markdown/markdown'
|
||||||
import 'codemirror/addon/edit/closebrackets'
|
import 'codemirror/addon/edit/closebrackets'
|
||||||
import 'codemirror/addon/edit/matchbrackets'
|
import 'codemirror/addon/edit/matchbrackets'
|
||||||
@ -23,6 +23,5 @@ import 'codemirror/addon/hint/css-hint'
|
|||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
app.use(createPinia())
|
app.use(createPinia())
|
||||||
app.use(ElementPlus)
|
|
||||||
|
|
||||||
app.mount(`#app`)
|
app.mount(`#app`)
|
||||||
|
@ -6,10 +6,10 @@ import { initRenderer } from '@/utils/renderer'
|
|||||||
import { useDark, useStorage, useToggle } from '@vueuse/core'
|
import { useDark, useStorage, useToggle } from '@vueuse/core'
|
||||||
|
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
||||||
import { marked } from 'marked'
|
import { marked } from 'marked'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { computed, markRaw, onMounted, ref, toRaw, watch } from 'vue'
|
import { computed, markRaw, onMounted, ref, toRaw, watch } from 'vue'
|
||||||
|
import { toast } from 'vue-sonner'
|
||||||
|
|
||||||
export const useStore = defineStore(`store`, () => {
|
export const useStore = defineStore(`store`, () => {
|
||||||
// 是否开启深色模式
|
// 是否开启深色模式
|
||||||
@ -265,6 +265,8 @@ export const useStore = defineStore(`store`, () => {
|
|||||||
|
|
||||||
updateCss()
|
updateCss()
|
||||||
editorRefresh()
|
editorRefresh()
|
||||||
|
|
||||||
|
toast.success(`样式重置成功~`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为函数添加刷新编辑器的功能
|
// 为函数添加刷新编辑器的功能
|
||||||
@ -361,7 +363,7 @@ export const useStore = defineStore(`store`, () => {
|
|||||||
reader.readAsText(file)
|
reader.readAsText(file)
|
||||||
reader.onload = (event) => {
|
reader.onload = (event) => {
|
||||||
(editor.value!).setValue((event.target!).result as string)
|
(editor.value!).setValue((event.target!).result as string)
|
||||||
ElMessage.success(`文档导入成功`)
|
toast.success(`文档导入成功`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,28 +372,11 @@ export const useStore = defineStore(`store`, () => {
|
|||||||
body.removeChild(input)
|
body.removeChild(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isOpenConfirmDialog = ref(false)
|
||||||
|
|
||||||
// 重置样式
|
// 重置样式
|
||||||
const resetStyleConfirm = () => {
|
const resetStyleConfirm = () => {
|
||||||
ElMessageBox.confirm(
|
isOpenConfirmDialog.value = true
|
||||||
`此操作将丢失本地自定义样式,是否继续?`,
|
|
||||||
`提示`,
|
|
||||||
{
|
|
||||||
confirmButtonText: `确定`,
|
|
||||||
cancelButtonText: `取消`,
|
|
||||||
type: `warning`,
|
|
||||||
center: true,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
resetStyle()
|
|
||||||
ElMessage({
|
|
||||||
type: `success`,
|
|
||||||
message: `样式重置成功~`,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
(editor.value!).focus()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -433,7 +418,9 @@ export const useStore = defineStore(`store`, () => {
|
|||||||
|
|
||||||
importMarkdownContent,
|
importMarkdownContent,
|
||||||
|
|
||||||
|
isOpenConfirmDialog,
|
||||||
resetStyleConfirm,
|
resetStyleConfirm,
|
||||||
|
resetStyle,
|
||||||
editorContent,
|
editorContent,
|
||||||
|
|
||||||
cssContentConfig,
|
cssContentConfig,
|
||||||
|
@ -186,8 +186,6 @@ export function exportHTML(primaryColor: string) {
|
|||||||
setStyles(element)
|
setStyles(element)
|
||||||
|
|
||||||
const htmlStr = element.innerHTML
|
const htmlStr = element.innerHTML
|
||||||
.replaceAll(`var(--el-text-color-regular)`, `#3f3f3f`)
|
|
||||||
.replaceAll(`var(--blockquote-background)`, `#f7f7f7`)
|
|
||||||
.replaceAll(`var(--md-primary-color)`, primaryColor)
|
.replaceAll(`var(--md-primary-color)`, primaryColor)
|
||||||
.replaceAll(/--md-primary-color:.+?;/g, ``)
|
.replaceAll(/--md-primary-color:.+?;/g, ``)
|
||||||
|
|
||||||
|
@ -4,8 +4,17 @@ import CssEditor from '@/components/CodemirrorEditor/CssEditor.vue'
|
|||||||
import EditorHeader from '@/components/CodemirrorEditor/EditorHeader/index.vue'
|
import EditorHeader from '@/components/CodemirrorEditor/EditorHeader/index.vue'
|
||||||
import InsertFormDialog from '@/components/CodemirrorEditor/InsertFormDialog.vue'
|
import InsertFormDialog from '@/components/CodemirrorEditor/InsertFormDialog.vue'
|
||||||
import UploadImgDialog from '@/components/CodemirrorEditor/UploadImgDialog.vue'
|
import UploadImgDialog from '@/components/CodemirrorEditor/UploadImgDialog.vue'
|
||||||
|
|
||||||
import RunLoading from '@/components/RunLoading.vue'
|
import RunLoading from '@/components/RunLoading.vue'
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from '@/components/ui/alert-dialog'
|
||||||
import {
|
import {
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
ContextMenuContent,
|
ContextMenuContent,
|
||||||
@ -14,7 +23,6 @@ import {
|
|||||||
ContextMenuShortcut,
|
ContextMenuShortcut,
|
||||||
ContextMenuTrigger,
|
ContextMenuTrigger,
|
||||||
} from '@/components/ui/context-menu'
|
} from '@/components/ui/context-menu'
|
||||||
|
|
||||||
import { altKey, altSign, ctrlKey, shiftKey, shiftSign } from '@/config'
|
import { altKey, altSign, ctrlKey, shiftKey, shiftSign } from '@/config'
|
||||||
import { useDisplayStore, useStore } from '@/stores'
|
import { useDisplayStore, useStore } from '@/stores'
|
||||||
import {
|
import {
|
||||||
@ -24,12 +32,9 @@ import {
|
|||||||
} from '@/utils'
|
} from '@/utils'
|
||||||
import fileApi from '@/utils/file'
|
import fileApi from '@/utils/file'
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
|
|
||||||
import { ElCol, ElMessage } from 'element-plus'
|
|
||||||
|
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
import { onMounted, ref, toRaw, watch } from 'vue'
|
import { onMounted, ref, toRaw, watch } from 'vue'
|
||||||
|
import { toast } from 'vue-sonner'
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const displayStore = useDisplayStore()
|
const displayStore = useDisplayStore()
|
||||||
@ -53,7 +58,7 @@ const {
|
|||||||
const isImgLoading = ref(false)
|
const isImgLoading = ref(false)
|
||||||
const timeout = ref<NodeJS.Timeout>()
|
const timeout = ref<NodeJS.Timeout>()
|
||||||
|
|
||||||
const preview = ref<typeof ElCol | null>(null)
|
const preview = ref<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
// 使浏览区与编辑区滚动条建立同步联系
|
// 使浏览区与编辑区滚动条建立同步联系
|
||||||
function leftAndRightScroll() {
|
function leftAndRightScroll() {
|
||||||
@ -63,7 +68,7 @@ function leftAndRightScroll() {
|
|||||||
|
|
||||||
clearTimeout(timeout.value)
|
clearTimeout(timeout.value)
|
||||||
if (text === `preview`) {
|
if (text === `preview`) {
|
||||||
source = preview.value!.$el
|
source = preview.value!
|
||||||
target = document.querySelector<HTMLElement>(`.CodeMirror-scroll`)!
|
target = document.querySelector<HTMLElement>(`.CodeMirror-scroll`)!
|
||||||
|
|
||||||
editor.value!.off(`scroll`, editorScrollCB)
|
editor.value!.off(`scroll`, editorScrollCB)
|
||||||
@ -73,7 +78,7 @@ function leftAndRightScroll() {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
source = document.querySelector<HTMLElement>(`.CodeMirror-scroll`)!
|
source = document.querySelector<HTMLElement>(`.CodeMirror-scroll`)!
|
||||||
target = preview.value!.$el
|
target = preview.value!
|
||||||
|
|
||||||
target.removeEventListener(`scroll`, previewScrollCB, false)
|
target.removeEventListener(`scroll`, previewScrollCB, false)
|
||||||
timeout.value = setTimeout(() => {
|
timeout.value = setTimeout(() => {
|
||||||
@ -96,7 +101,7 @@ function leftAndRightScroll() {
|
|||||||
scrollCB(`preview`)
|
scrollCB(`preview`)
|
||||||
}
|
}
|
||||||
|
|
||||||
(preview.value!.$el).addEventListener(`scroll`, previewScrollCB, false)
|
(preview.value!).addEventListener(`scroll`, previewScrollCB, false)
|
||||||
editor.value!.on(`scroll`, editorScrollCB)
|
editor.value!.on(`scroll`, editorScrollCB)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +136,7 @@ function beforeUpload(file: File) {
|
|||||||
// validate image
|
// validate image
|
||||||
const checkResult = checkImage(file)
|
const checkResult = checkImage(file)
|
||||||
if (!checkResult.ok) {
|
if (!checkResult.ok) {
|
||||||
ElMessage.error(checkResult.msg)
|
toast.error(checkResult.msg!)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +147,7 @@ function beforeUpload(file: File) {
|
|||||||
const config = localStorage.getItem(`${imgHost}Config`)
|
const config = localStorage.getItem(`${imgHost}Config`)
|
||||||
const isValidHost = imgHost === `default` || config
|
const isValidHost = imgHost === `default` || config
|
||||||
if (!isValidHost) {
|
if (!isValidHost) {
|
||||||
ElMessage.error(`请先配置 ${imgHost} 图床参数`)
|
toast.error(`请先配置 ${imgHost} 图床参数`)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -151,7 +156,7 @@ function beforeUpload(file: File) {
|
|||||||
// 图片上传结束
|
// 图片上传结束
|
||||||
function uploaded(imageUrl: string) {
|
function uploaded(imageUrl: string) {
|
||||||
if (!imageUrl) {
|
if (!imageUrl) {
|
||||||
ElMessage.error(`上传图片未知异常`)
|
toast.error(`上传图片未知异常`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
toggleShowUploadImgDialog(false)
|
toggleShowUploadImgDialog(false)
|
||||||
@ -160,7 +165,7 @@ function uploaded(imageUrl: string) {
|
|||||||
const markdownImage = `![](${imageUrl})`
|
const markdownImage = `![](${imageUrl})`
|
||||||
// 将 Markdown 形式的 URL 插入编辑框光标所在位置
|
// 将 Markdown 形式的 URL 插入编辑框光标所在位置
|
||||||
toRaw(store.editor!).replaceSelection(`\n${markdownImage}\n`, cursor as any)
|
toRaw(store.editor!).replaceSelection(`\n${markdownImage}\n`, cursor as any)
|
||||||
ElMessage.success(`图片上传成功`)
|
toast.success(`图片上传成功`)
|
||||||
}
|
}
|
||||||
function uploadImage(file: File, cb?: { (url: any): void, (arg0: unknown): void } | undefined) {
|
function uploadImage(file: File, cb?: { (url: any): void, (arg0: unknown): void } | undefined) {
|
||||||
isImgLoading.value = true
|
isImgLoading.value = true
|
||||||
@ -176,7 +181,7 @@ function uploadImage(file: File, cb?: { (url: any): void, (arg0: unknown): void
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
ElMessage.error(err.message)
|
toast.error(err.message)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
isImgLoading.value = false
|
isImgLoading.value = false
|
||||||
@ -274,12 +279,12 @@ function addFormat(cmd: string | number) {
|
|||||||
(editor.value as any).options.extraKeys[cmd](editor.value)
|
(editor.value as any).options.extraKeys[cmd](editor.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const codeMirrorWrapper = ref<ComponentPublicInstance<typeof ElCol> | null>(null)
|
const codeMirrorWrapper = ref<ComponentPublicInstance<HTMLDivElement> | null>(null)
|
||||||
|
|
||||||
// 转换 markdown 中的本地图片为线上图片
|
// 转换 markdown 中的本地图片为线上图片
|
||||||
// todo 处理事件覆盖
|
// todo 处理事件覆盖
|
||||||
function mdLocalToRemote() {
|
function mdLocalToRemote() {
|
||||||
const dom = codeMirrorWrapper.value!.$el as HTMLElement
|
const dom = codeMirrorWrapper.value!
|
||||||
|
|
||||||
// 上传 md 中的图片
|
// 上传 md 中的图片
|
||||||
const uploadMdImg = async ({ md, list }: { md: { str: string, path: string, file: File }, list: { path: string, file: File }[] }) => {
|
const uploadMdImg = async ({ md, list }: { md: { str: string, path: string, file: File }, list: { path: string, file: File }[] }) => {
|
||||||
@ -391,10 +396,9 @@ onMounted(() => {
|
|||||||
@end-copy="endCopy"
|
@end-copy="endCopy"
|
||||||
/>
|
/>
|
||||||
<main class="container-main flex-1">
|
<main class="container-main flex-1">
|
||||||
<el-row class="container-main-section h-full border-1">
|
<div class="container-main-section grid h-full border-1" :class="isShowCssEditor ? 'grid-cols-3' : 'grid-cols-2'">
|
||||||
<ElCol
|
<div
|
||||||
ref="codeMirrorWrapper"
|
ref="codeMirrorWrapper"
|
||||||
:span="isShowCssEditor ? 8 : 12"
|
|
||||||
class="codeMirror-wrapper border-r-1"
|
class="codeMirror-wrapper border-r-1"
|
||||||
:class="{
|
:class="{
|
||||||
'order-1': !store.isEditOnLeft,
|
'order-1': !store.isEditOnLeft,
|
||||||
@ -434,8 +438,8 @@ onMounted(() => {
|
|||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</ElCol>
|
</div>
|
||||||
<ElCol
|
<div
|
||||||
id="preview"
|
id="preview"
|
||||||
ref="preview"
|
ref="preview"
|
||||||
:span="isShowCssEditor ? 8 : 12"
|
:span="isShowCssEditor ? 8 : 12"
|
||||||
@ -452,9 +456,9 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ElCol>
|
</div>
|
||||||
<CssEditor />
|
<CssEditor />
|
||||||
</el-row>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<UploadImgDialog
|
<UploadImgDialog
|
||||||
@ -464,6 +468,23 @@ onMounted(() => {
|
|||||||
<InsertFormDialog />
|
<InsertFormDialog />
|
||||||
|
|
||||||
<RunLoading />
|
<RunLoading />
|
||||||
|
|
||||||
|
<AlertDialog v-model:open="store.isOpenConfirmDialog">
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>提示</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
此操作将丢失本地自定义样式,是否继续?
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||||
|
<AlertDialogAction @click="store.resetStyle()">
|
||||||
|
确认
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -497,8 +518,8 @@ onMounted(() => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--el-text-color-regular);
|
color: hsl(var(--foreground));
|
||||||
background-color: var(--el-bg-color);
|
background-color: hsl(var(--background));
|
||||||
|
|
||||||
.loading-mask-box {
|
.loading-mask-box {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
|
@ -5,7 +5,6 @@ import vue from '@vitejs/plugin-vue'
|
|||||||
import { visualizer } from 'rollup-plugin-visualizer'
|
import { visualizer } from 'rollup-plugin-visualizer'
|
||||||
import UnoCSS from 'unocss/vite'
|
import UnoCSS from 'unocss/vite'
|
||||||
import AutoImport from 'unplugin-auto-import/vite'
|
import AutoImport from 'unplugin-auto-import/vite'
|
||||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
|
||||||
import Components from 'unplugin-vue-components/vite'
|
import Components from 'unplugin-vue-components/vite'
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import { nodePolyfills } from 'vite-plugin-node-polyfills'
|
import { nodePolyfills } from 'vite-plugin-node-polyfills'
|
||||||
@ -33,10 +32,10 @@ export default defineConfig({
|
|||||||
filename: `stats.html`,
|
filename: `stats.html`,
|
||||||
}),
|
}),
|
||||||
AutoImport({
|
AutoImport({
|
||||||
resolvers: [ElementPlusResolver()],
|
resolvers: [],
|
||||||
}),
|
}),
|
||||||
Components({
|
Components({
|
||||||
resolvers: [ElementPlusResolver()],
|
resolvers: [],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
|
Loading…
Reference in New Issue
Block a user