feat: 添加登录功能与文章编辑功能

refactor: 重构API服务与全局状态管理

style: 优化UI样式与布局

fix: 修复文章列表与详情页的显示问题

docs: 更新类型定义与注释

chore: 更新依赖包与配置文件
This commit is contained in:
qingfeng1121
2025-10-30 19:00:59 +08:00
parent 85bf3214cc
commit 6d90b5842f
27 changed files with 2400 additions and 304 deletions

View File

@@ -1,6 +1,4 @@
{
"include": ["src/**/*"],
"compilerOptions": {
"paths": {

822
package-lock.json generated
View File

@@ -14,6 +14,7 @@
"antd": "^5.27.3",
"axios": "^1.12.2",
"element-plus": "^2.11.5",
"md-editor-v3": "^6.1.0",
"naive-ui": "^2.43.1",
"pinia": "^3.0.3",
"sass": "^1.93.2",
@@ -672,6 +673,404 @@
"node": ">=6.9.0"
}
},
"node_modules/@codemirror/autocomplete": {
"version": "6.19.1",
"resolved": "https://registry.npmmirror.com/@codemirror/autocomplete/-/autocomplete-6.19.1.tgz",
"integrity": "sha512-q6NenYkEy2fn9+JyjIxMWcNjzTL/IhwqfzOut1/G3PrIFkrbl4AL7Wkse5tLrQUUyqGoAKU5+Pi5jnnXxH5HGw==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.17.0",
"@lezer/common": "^1.0.0"
}
},
"node_modules/@codemirror/commands": {
"version": "6.10.0",
"resolved": "https://registry.npmmirror.com/@codemirror/commands/-/commands-6.10.0.tgz",
"integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.4.0",
"@codemirror/view": "^6.27.0",
"@lezer/common": "^1.1.0"
}
},
"node_modules/@codemirror/lang-angular": {
"version": "0.1.4",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-angular/-/lang-angular-0.1.4.tgz",
"integrity": "sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g==",
"license": "MIT",
"dependencies": {
"@codemirror/lang-html": "^6.0.0",
"@codemirror/lang-javascript": "^6.1.2",
"@codemirror/language": "^6.0.0",
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.3.3"
}
},
"node_modules/@codemirror/lang-cpp": {
"version": "6.0.3",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-cpp/-/lang-cpp-6.0.3.tgz",
"integrity": "sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@lezer/cpp": "^1.0.0"
}
},
"node_modules/@codemirror/lang-css": {
"version": "6.3.1",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
"integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@lezer/common": "^1.0.2",
"@lezer/css": "^1.1.7"
}
},
"node_modules/@codemirror/lang-go": {
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-go/-/lang-go-6.0.1.tgz",
"integrity": "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.6.0",
"@codemirror/state": "^6.0.0",
"@lezer/common": "^1.0.0",
"@lezer/go": "^1.0.0"
}
},
"node_modules/@codemirror/lang-html": {
"version": "6.4.11",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-html/-/lang-html-6.4.11.tgz",
"integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/lang-css": "^6.0.0",
"@codemirror/lang-javascript": "^6.0.0",
"@codemirror/language": "^6.4.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.17.0",
"@lezer/common": "^1.0.0",
"@lezer/css": "^1.1.0",
"@lezer/html": "^1.3.12"
}
},
"node_modules/@codemirror/lang-java": {
"version": "6.0.2",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-java/-/lang-java-6.0.2.tgz",
"integrity": "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@lezer/java": "^1.0.0"
}
},
"node_modules/@codemirror/lang-javascript": {
"version": "6.2.4",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz",
"integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.6.0",
"@codemirror/lint": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.17.0",
"@lezer/common": "^1.0.0",
"@lezer/javascript": "^1.0.0"
}
},
"node_modules/@codemirror/lang-jinja": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-jinja/-/lang-jinja-6.0.0.tgz",
"integrity": "sha512-47MFmRcR8UAxd8DReVgj7WJN1WSAMT7OJnewwugZM4XiHWkOjgJQqvEM1NpMj9ALMPyxmlziEI1opH9IaEvmaw==",
"license": "MIT",
"dependencies": {
"@codemirror/lang-html": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.2.0",
"@lezer/lr": "^1.4.0"
}
},
"node_modules/@codemirror/lang-json": {
"version": "6.0.2",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-json/-/lang-json-6.0.2.tgz",
"integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@lezer/json": "^1.0.0"
}
},
"node_modules/@codemirror/lang-less": {
"version": "6.0.2",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-less/-/lang-less-6.0.2.tgz",
"integrity": "sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==",
"license": "MIT",
"dependencies": {
"@codemirror/lang-css": "^6.2.0",
"@codemirror/language": "^6.0.0",
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@codemirror/lang-liquid": {
"version": "6.3.0",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-liquid/-/lang-liquid-6.3.0.tgz",
"integrity": "sha512-fY1YsUExcieXRTsCiwX/bQ9+PbCTA/Fumv7C7mTUZHoFkibfESnaXwpr2aKH6zZVwysEunsHHkaIpM/pl3xETQ==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/lang-html": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@lezer/common": "^1.0.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.3.1"
}
},
"node_modules/@codemirror/lang-markdown": {
"version": "6.5.0",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz",
"integrity": "sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.7.1",
"@codemirror/lang-html": "^6.0.0",
"@codemirror/language": "^6.3.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@lezer/common": "^1.2.1",
"@lezer/markdown": "^1.0.0"
}
},
"node_modules/@codemirror/lang-php": {
"version": "6.0.2",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-php/-/lang-php-6.0.2.tgz",
"integrity": "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==",
"license": "MIT",
"dependencies": {
"@codemirror/lang-html": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@lezer/common": "^1.0.0",
"@lezer/php": "^1.0.0"
}
},
"node_modules/@codemirror/lang-python": {
"version": "6.2.1",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-python/-/lang-python-6.2.1.tgz",
"integrity": "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.3.2",
"@codemirror/language": "^6.8.0",
"@codemirror/state": "^6.0.0",
"@lezer/common": "^1.2.1",
"@lezer/python": "^1.1.4"
}
},
"node_modules/@codemirror/lang-rust": {
"version": "6.0.2",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-rust/-/lang-rust-6.0.2.tgz",
"integrity": "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@lezer/rust": "^1.0.0"
}
},
"node_modules/@codemirror/lang-sass": {
"version": "6.0.2",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-sass/-/lang-sass-6.0.2.tgz",
"integrity": "sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==",
"license": "MIT",
"dependencies": {
"@codemirror/lang-css": "^6.2.0",
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@lezer/common": "^1.0.2",
"@lezer/sass": "^1.0.0"
}
},
"node_modules/@codemirror/lang-sql": {
"version": "6.10.0",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-sql/-/lang-sql-6.10.0.tgz",
"integrity": "sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@codemirror/lang-vue": {
"version": "0.1.3",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-vue/-/lang-vue-0.1.3.tgz",
"integrity": "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==",
"license": "MIT",
"dependencies": {
"@codemirror/lang-html": "^6.0.0",
"@codemirror/lang-javascript": "^6.1.2",
"@codemirror/language": "^6.0.0",
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.3.1"
}
},
"node_modules/@codemirror/lang-wast": {
"version": "6.0.2",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-wast/-/lang-wast-6.0.2.tgz",
"integrity": "sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@codemirror/lang-xml": {
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz",
"integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.4.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@lezer/common": "^1.0.0",
"@lezer/xml": "^1.0.0"
}
},
"node_modules/@codemirror/lang-yaml": {
"version": "6.1.2",
"resolved": "https://registry.npmmirror.com/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz",
"integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.2.0",
"@lezer/lr": "^1.0.0",
"@lezer/yaml": "^1.0.0"
}
},
"node_modules/@codemirror/language": {
"version": "6.11.3",
"resolved": "https://registry.npmmirror.com/@codemirror/language/-/language-6.11.3.tgz",
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0",
"@lezer/common": "^1.1.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0",
"style-mod": "^4.0.0"
}
},
"node_modules/@codemirror/language-data": {
"version": "6.5.2",
"resolved": "https://registry.npmmirror.com/@codemirror/language-data/-/language-data-6.5.2.tgz",
"integrity": "sha512-CPkWBKrNS8stYbEU5kwBwTf3JB1kghlbh4FSAwzGW2TEscdeHHH4FGysREW86Mqnj3Qn09s0/6Ea/TutmoTobg==",
"license": "MIT",
"dependencies": {
"@codemirror/lang-angular": "^0.1.0",
"@codemirror/lang-cpp": "^6.0.0",
"@codemirror/lang-css": "^6.0.0",
"@codemirror/lang-go": "^6.0.0",
"@codemirror/lang-html": "^6.0.0",
"@codemirror/lang-java": "^6.0.0",
"@codemirror/lang-javascript": "^6.0.0",
"@codemirror/lang-jinja": "^6.0.0",
"@codemirror/lang-json": "^6.0.0",
"@codemirror/lang-less": "^6.0.0",
"@codemirror/lang-liquid": "^6.0.0",
"@codemirror/lang-markdown": "^6.0.0",
"@codemirror/lang-php": "^6.0.0",
"@codemirror/lang-python": "^6.0.0",
"@codemirror/lang-rust": "^6.0.0",
"@codemirror/lang-sass": "^6.0.0",
"@codemirror/lang-sql": "^6.0.0",
"@codemirror/lang-vue": "^0.1.1",
"@codemirror/lang-wast": "^6.0.0",
"@codemirror/lang-xml": "^6.0.0",
"@codemirror/lang-yaml": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/legacy-modes": "^6.4.0"
}
},
"node_modules/@codemirror/legacy-modes": {
"version": "6.5.2",
"resolved": "https://registry.npmmirror.com/@codemirror/legacy-modes/-/legacy-modes-6.5.2.tgz",
"integrity": "sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0"
}
},
"node_modules/@codemirror/lint": {
"version": "6.9.1",
"resolved": "https://registry.npmmirror.com/@codemirror/lint/-/lint-6.9.1.tgz",
"integrity": "sha512-te7To1EQHePBQQzasDKWmK2xKINIXpk+xAiSYr9ZN+VB4KaT+/Hi2PEkeErTk5BV3PTz1TLyQL4MtJfPkKZ9sw==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.35.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/search": {
"version": "6.5.11",
"resolved": "https://registry.npmmirror.com/@codemirror/search/-/search-6.5.11.tgz",
"integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/state": {
"version": "6.5.2",
"resolved": "https://registry.npmmirror.com/@codemirror/state/-/state-6.5.2.tgz",
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
"license": "MIT",
"dependencies": {
"@marijn/find-cluster-break": "^1.0.0"
}
},
"node_modules/@codemirror/view": {
"version": "6.38.6",
"resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.38.6.tgz",
"integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.5.0",
"crelt": "^1.0.6",
"style-mod": "^4.1.0",
"w3c-keyname": "^2.2.4"
}
},
"node_modules/@css-render/plugin-bem": {
"version": "0.15.14",
"resolved": "https://registry.npmmirror.com/@css-render/plugin-bem/-/plugin-bem-0.15.14.tgz",
@@ -1375,6 +1774,189 @@
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==",
"license": "Apache-2.0"
},
"node_modules/@lezer/common": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/@lezer/common/-/common-1.3.0.tgz",
"integrity": "sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ==",
"license": "MIT"
},
"node_modules/@lezer/cpp": {
"version": "1.1.3",
"resolved": "https://registry.npmmirror.com/@lezer/cpp/-/cpp-1.1.3.tgz",
"integrity": "sha512-ykYvuFQKGsRi6IcE+/hCSGUhb/I4WPjd3ELhEblm2wS2cOznDFzO+ubK2c+ioysOnlZ3EduV+MVQFCPzAIoY3w==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/css": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/@lezer/css/-/css-1.3.0.tgz",
"integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.3.0"
}
},
"node_modules/@lezer/go": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/@lezer/go/-/go-1.0.1.tgz",
"integrity": "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.3.0"
}
},
"node_modules/@lezer/highlight": {
"version": "1.2.3",
"resolved": "https://registry.npmmirror.com/@lezer/highlight/-/highlight-1.2.3.tgz",
"integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.3.0"
}
},
"node_modules/@lezer/html": {
"version": "1.3.12",
"resolved": "https://registry.npmmirror.com/@lezer/html/-/html-1.3.12.tgz",
"integrity": "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/java": {
"version": "1.1.3",
"resolved": "https://registry.npmmirror.com/@lezer/java/-/java-1.1.3.tgz",
"integrity": "sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/javascript": {
"version": "1.5.4",
"resolved": "https://registry.npmmirror.com/@lezer/javascript/-/javascript-1.5.4.tgz",
"integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.1.3",
"@lezer/lr": "^1.3.0"
}
},
"node_modules/@lezer/json": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/@lezer/json/-/json-1.0.3.tgz",
"integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/lr": {
"version": "1.4.2",
"resolved": "https://registry.npmmirror.com/@lezer/lr/-/lr-1.4.2.tgz",
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@lezer/markdown": {
"version": "1.5.1",
"resolved": "https://registry.npmmirror.com/@lezer/markdown/-/markdown-1.5.1.tgz",
"integrity": "sha512-F3ZFnIfNAOy/jPSk6Q0e3bs7e9grfK/n5zerkKoc5COH6Guy3Zb0vrJwXzdck79K16goBhYBRAvhf+ksqe0cMg==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.0.0",
"@lezer/highlight": "^1.0.0"
}
},
"node_modules/@lezer/php": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/@lezer/php/-/php-1.0.5.tgz",
"integrity": "sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.1.0"
}
},
"node_modules/@lezer/python": {
"version": "1.1.18",
"resolved": "https://registry.npmmirror.com/@lezer/python/-/python-1.1.18.tgz",
"integrity": "sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/rust": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/@lezer/rust/-/rust-1.0.2.tgz",
"integrity": "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/sass": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/@lezer/sass/-/sass-1.1.0.tgz",
"integrity": "sha512-3mMGdCTUZ/84ArHOuXWQr37pnf7f+Nw9ycPUeKX+wu19b7pSMcZGLbaXwvD2APMBDOGxPmpK/O6S1v1EvLoqgQ==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/xml": {
"version": "1.0.6",
"resolved": "https://registry.npmmirror.com/@lezer/xml/-/xml-1.0.6.tgz",
"integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/yaml": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/@lezer/yaml/-/yaml-1.0.3.tgz",
"integrity": "sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.4.0"
}
},
"node_modules/@marijn/find-cluster-break": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
"license": "MIT"
},
"node_modules/@one-ini/wasm": {
"version": "0.1.1",
"resolved": "https://registry.npmmirror.com/@one-ini/wasm/-/wasm-0.1.1.tgz",
@@ -2217,6 +2799,12 @@
"integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==",
"license": "MIT"
},
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"license": "MIT"
},
"node_modules/@types/lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.20.tgz",
@@ -2232,6 +2820,22 @@
"@types/lodash": "*"
}
},
"node_modules/@types/markdown-it": {
"version": "14.1.2",
"resolved": "https://registry.npmmirror.com/@types/markdown-it/-/markdown-it-14.1.2.tgz",
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
"license": "MIT",
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
}
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"license": "MIT"
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz",
@@ -2245,6 +2849,18 @@
"integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==",
"license": "MIT"
},
"node_modules/@vavt/copy2clipboard": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/@vavt/copy2clipboard/-/copy2clipboard-1.0.3.tgz",
"integrity": "sha512-HtG48r2FBYp9eRvGB3QGmtRBH1zzRRAVvFbGgFstOwz4/DDaNiX0uZc3YVKPydqgOav26pibr9MtoCaWxn7aeA==",
"license": "MIT"
},
"node_modules/@vavt/util": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/@vavt/util/-/util-2.1.0.tgz",
"integrity": "sha512-YIfAvArSFVXmWvoF+DEGD0FhkhVNcCtVWWkfYtj76eSrwHh/wuEEFhiEubg1XLNM3tChO8FH8xJCT/hnizjgFQ==",
"license": "MIT"
},
"node_modules/@vicons/ionicons5": {
"version": "0.13.0",
"resolved": "https://registry.npmmirror.com/@vicons/ionicons5/-/ionicons5-0.13.0.tgz",
@@ -2994,6 +3610,12 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/array-tree-filter": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz",
@@ -3236,6 +3858,21 @@
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
"license": "MIT"
},
"node_modules/codemirror": {
"version": "6.0.2",
"resolved": "https://registry.npmmirror.com/codemirror/-/codemirror-6.0.2.tgz",
"integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/commands": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/lint": "^6.0.0",
"@codemirror/search": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
@@ -3344,6 +3981,12 @@
"url": "https://opencollective.com/core-js"
}
},
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmmirror.com/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -3375,6 +4018,12 @@
"integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==",
"license": "MIT"
},
"node_modules/cssfilter": {
"version": "0.0.10",
"resolved": "https://registry.npmmirror.com/cssfilter/-/cssfilter-0.0.10.tgz",
"integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==",
"license": "MIT"
},
"node_modules/cssstyle": {
"version": "4.6.0",
"resolved": "https://registry.npmmirror.com/cssstyle/-/cssstyle-4.6.0.tgz",
@@ -4534,6 +5183,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/local-pkg": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.2.tgz",
@@ -4601,6 +5259,15 @@
"dev": true,
"license": "ISC"
},
"node_modules/lucide-vue-next": {
"version": "0.543.0",
"resolved": "https://registry.npmmirror.com/lucide-vue-next/-/lucide-vue-next-0.543.0.tgz",
"integrity": "sha512-Az5kpNm/koKAwSNIKjsZ4uHV2tVfmlQlcHwFBygQ8gc5/jFg7An9OrxgDy/aE5m+HLx7VfLYqDxLr8gWecZbQA==",
"license": "ISC",
"peerDependencies": {
"vue": ">=3.0.1"
}
},
"node_modules/magic-string": {
"version": "0.30.19",
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.19.tgz",
@@ -4610,6 +5277,59 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/markdown-it": {
"version": "14.1.0",
"resolved": "https://registry.npmmirror.com/markdown-it/-/markdown-it-14.1.0.tgz",
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
"linkify-it": "^5.0.0",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/markdown-it-image-figures": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/markdown-it-image-figures/-/markdown-it-image-figures-2.1.1.tgz",
"integrity": "sha512-mwXSQ2nPeVUzCMIE3HlLvjRioopiqyJLNph0pyx38yf9mpqFDhNGnMpAXF9/A2Xv0oiF2cVyg9xwfF0HNAz05g==",
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"markdown-it": "*"
}
},
"node_modules/markdown-it-sub": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/markdown-it-sub/-/markdown-it-sub-2.0.0.tgz",
"integrity": "sha512-iCBKgwCkfQBRg2vApy9vx1C1Tu6D8XYo8NvevI3OlwzBRmiMtsJ2sXupBgEA7PPxiDwNni3qIUkhZ6j5wofDUA==",
"license": "MIT"
},
"node_modules/markdown-it-sup": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/markdown-it-sup/-/markdown-it-sup-2.0.0.tgz",
"integrity": "sha512-5VgmdKlkBd8sgXuoDoxMpiU+BiEt3I49GItBzzw7Mxq9CxvnhE/k09HFli09zgfFDRixDQDfDxi0mgBCXtaTvA==",
"license": "MIT"
},
"node_modules/markdown-it/node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -4619,6 +5339,59 @@
"node": ">= 0.4"
}
},
"node_modules/md-editor-v3": {
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/md-editor-v3/-/md-editor-v3-6.1.0.tgz",
"integrity": "sha512-8/3cNyAt2CfMhu3PiQZqpOmCKmBXa7uJzWHnATVwkv61TOgUBuho4IFQPougmM4J7qZhZ0sldXQHDVf1BjABxg==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.18.7",
"@codemirror/commands": "^6.8.1",
"@codemirror/lang-markdown": "^6.3.4",
"@codemirror/language": "^6.11.3",
"@codemirror/language-data": "^6.5.1",
"@codemirror/search": "^6.5.11",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.38.2",
"@lezer/highlight": "^1.2.1",
"@types/markdown-it": "^14.0.1",
"@vavt/copy2clipboard": "^1.0.1",
"@vavt/util": "^2.1.0",
"codemirror": "^6.0.2",
"lru-cache": "^11.2.1",
"lucide-vue-next": "^0.543.0",
"markdown-it": "^14.0.0",
"markdown-it-image-figures": "^2.1.1",
"markdown-it-sub": "^2.0.0",
"markdown-it-sup": "^2.0.0",
"medium-zoom": "^1.1.0",
"xss": "^1.0.15"
},
"peerDependencies": {
"vue": "^3.5.3"
}
},
"node_modules/md-editor-v3/node_modules/lru-cache": {
"version": "11.2.2",
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.2.2.tgz",
"integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==",
"license": "ISC",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"license": "MIT"
},
"node_modules/medium-zoom": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/medium-zoom/-/medium-zoom-1.1.0.tgz",
"integrity": "sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ==",
"license": "MIT"
},
"node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
@@ -5152,6 +5925,15 @@
"node": ">=6"
}
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/quansync": {
"version": "0.2.11",
"resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz",
@@ -6252,6 +7034,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/style-mod": {
"version": "4.1.3",
"resolved": "https://registry.npmmirror.com/style-mod/-/style-mod-4.1.3.tgz",
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
"license": "MIT"
},
"node_modules/stylis": {
"version": "4.3.6",
"resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.6.tgz",
@@ -6428,6 +7216,12 @@
"integrity": "sha512-M8RGFoKtZ8dF+iwJfAJTOH/SM4KluKOKRJpjCMhI8bG3qB74zrFoArKZ62ll0Fr3mqkMJiQOmWYkdYgDeITYQg==",
"license": "MIT"
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"license": "MIT"
},
"node_modules/ufo": {
"version": "1.6.1",
"resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.1.tgz",
@@ -7178,6 +7972,12 @@
"vue": "^3.0.11"
}
},
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmmirror.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"license": "MIT"
},
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
@@ -7440,6 +8240,28 @@
"dev": true,
"license": "MIT"
},
"node_modules/xss": {
"version": "1.0.15",
"resolved": "https://registry.npmmirror.com/xss/-/xss-1.0.15.tgz",
"integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==",
"license": "MIT",
"dependencies": {
"commander": "^2.20.3",
"cssfilter": "0.0.10"
},
"bin": {
"xss": "bin/xss"
},
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/xss/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"license": "MIT"
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",

View File

@@ -19,6 +19,7 @@
"antd": "^5.27.3",
"axios": "^1.12.2",
"element-plus": "^2.11.5",
"md-editor-v3": "^6.1.0",
"naive-ui": "^2.43.1",
"pinia": "^3.0.3",
"sass": "^1.93.2",

View File

@@ -1,5 +1,5 @@
<template>
<div id="app">
<div id="apps">
<MainLayout />
</div>
</template>
@@ -14,7 +14,7 @@ import MainLayout from './layouts/MainLayout.vue'
padding: 0;
}
#app {
#apps {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;

View File

@@ -103,14 +103,14 @@ onUnmounted(() => {
background-color: rgba(102, 161, 216, 0.9); /* 蓝色半透明背景 */
}
.top1 {
padding-top: 10px;
#alld #top .top1 {
padding-top: 20px;
margin-bottom: 0;
text-align: center;
color: white;
}
.top2 {
padding-top: 10px;
#alld #top .top2 {
text-align: center;
color: white;
}
@@ -123,7 +123,7 @@ onUnmounted(() => {
.cont1 {
text-align: center;
padding: 25px;
padding: 25px 10px 25px 10px ;
background-color: rgba(102, 161, 216, 0.9); /* 蓝色半透明背景 */
border-radius: 10px 10px 0 0;
}

View File

@@ -31,7 +31,7 @@
<!-- 搜索功能 -->
<div class="search-wrapper">
<button class="search-icon-btn" @click="toggleSearchBox" :class="{ 'active': isSearchBoxOpen }">
<svg t="1761567058506" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5586" width="32" height="32"><path d="M512 858.3168c-194.816 0-352-166.2464-352-370.4832S317.184 117.3504 512 117.3504s352 166.2464 352 370.4832-157.184 370.4832-352 370.4832z m0-64c158.6688 0 288-136.8576 288-306.4832 0-169.6768-129.3312-306.4832-288-306.4832S224 318.1568 224 487.8336c0 169.6256 129.3312 306.4832 288 306.4832zM717.312 799.9488a32 32 0 0 1 46.4896-43.9808l91.4432 96.7168a32 32 0 0 1-46.4896 43.9808l-91.4432-96.768z" fill="#5A5A68" p-id="5587"></path></svg>
</button>
<div class="search-box-container" :class="{ 'open': isSearchBoxOpen }">
<el-input
@@ -71,19 +71,21 @@
<div class="Pagination">
<!-- 分页组件可以在这里添加 -->
</div>
<Establish class="establish-container" v-if="Login" />
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { ref, onMounted, onUnmounted, watch, computed } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import LeftModule from '@/components/LeftModule.vue';
import Establish from '@/layouts/establish.vue'
// 路由相关
const router = useRouter();
const route = useRoute();
// 全局状态管理
import { useGlobalStore } from '@/store/globalStore'
const globalStore = useGlobalStore()
const Login = computed(() => globalStore.Login)
// 响应式状态
const classhero = ref(false);
@@ -129,7 +131,7 @@ const startTypewriter = () => {
* 菜单选择跳转
*/
const handleSelect = (key: string) => {
globalStore.clearAll()
// globalStore.clearAll()
router.push({ path: '/' + key });
};
@@ -171,8 +173,11 @@ const closeSearchBoxWithDelay = () => {
const performSearch = () => {
if (searchKeyword.value.trim()) {
// 这里可以根据实际需求实现搜索逻辑
console.log('搜索关键词:', searchKeyword.value);
router.push({ path: `/home/aericletitle/${searchKeyword.value}`});
// 存储文章信息到全局状态
globalStore.setValue('articleserarch', {
name: searchKeyword.value
})
router.push({ path: `/home/aericletitle`});
}
// 搜索后保持搜索框打开状态
@@ -193,7 +198,14 @@ const updatePageState = (url: string) => {
* 设置当前激活的菜单项
*/
const setActiveIndex = (path: string) => {
console.log('设置激活索引:', path);
// 存储文章信息到全局状态
globalStore.setValue('localpath', {
name: path
})
// console.log('设置激活索引:', path);
if (path === 'message') {
globalStore.removeValue('articleInfo')
}
activeIndex.value =path;
};

293
src/layouts/establish.vue Normal file
View File

@@ -0,0 +1,293 @@
<template>
<div class="establish-container">
<!-- 弹出的按钮容器 -->
<!-- <div class="expanded-buttons" :class="{ 'show': isExpanded }"> -->
<button v-for="(btn) in isbuttonsave()" :class="{ 'show': isExpanded }" :key="btn.id" class="action-button"
@click="handleButtonClick(btn)">
<!-- <i :class="btn.icon"></i> -->
<span>{{ btn.label }}</span>
</button>
<!-- </div> -->
<!-- 主圆形按钮 -->
<button class="main-button" :class="{ 'active': isExpanded }" @click="toggleExpand">
<i class="icon">+</i>
</button>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { useRouter } from 'vue-router';
import { articleService, messageService, categoryAttributeService } from '@/services'
// 全局状态管理
import { useGlobalStore } from '@/store/globalStore'
const globalStore = useGlobalStore()
// 路由参数
const router = useRouter()
// 定义响应式状态
const isExpanded = ref(false)
const buttons = [
{ id: 1, label: '新建文章', icon: 'icon-add-article' },
{ id: 2, label: '新建分类', icon: 'icon-create-category' },
{ id: 3, label: '疯言疯语', icon: 'icon-upload-file' },
{ id: 4, label: '新建标签', icon: 'icon-new-tag' }
]
const buttonsave = [
{ id: 1, label: '修改文章', icon: 'icon-add-article' },
{ id: 2, label: '删除删除', icon: 'icon-create-category' },
]
const isbuttonsave = () => {
if (globalStore.getValue('localpath').name == 'article') {
return buttonsave
}
return buttons
}
// 切换按钮显示状态
const toggleExpand = (event) => {
isExpanded.value = !isExpanded.value
// 防止点击弹出的按钮后再次触发主按钮的点击事件
event.stopPropagation()
}
// 处理按钮点击事件
const handleButtonClick = (button) => {
console.log('点击了按钮:', button.label)
// 可以在这里添加具体的按钮点击逻辑
if (button.label == '新建文章') {
router.push({
path: '/articlesave',
})
}
if (button.label == '新建分类') {
globalStore.setValue('localpath', { name: 'category', id: 0 })
}
if (button.label == '疯言疯语') {
globalStore.setValue('localpath', { name: 'message', id: 0 })
}
if (button.label == '新建标签') {
globalStore.setValue('localpath', { name: 'tag', id: 0 })
}
if (button.label == '修改文章') {
globalStore.setValue('localpath', { name: 'article', id: Number(route.query.id) })
}
if (button.label == '删除删除') {
// 确认删除
ElMessageBox.confirm('确定删除该文章吗?', '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 调用删除文章接口
articleService.deleteArticle(Number(route.query.id)).then(response => {
if (response.code == 200) {
ElMessage.success('文章删除成功')
// 跳转回文章列表页
router.push({ path: '/home' })
} else {
ElMessage.error(response.message || '删除失败')
}
}).catch(err => {
ElMessage.error(err.message || '删除失败')
})
}).catch(() => {
// 取消删除
})
}
isExpanded.value = false // 点击后收起
}
// 点击外部区域收起按钮的处理函数
const handleClickOutside = (e) => {
const mainButton = document.querySelector('.main-button')
const actionButtons = document.querySelectorAll('.action-button')
// 检查是否点击了主按钮
const clickedMainButton = mainButton && mainButton.contains(e.target)
// 检查是否点击了任何一个操作按钮
const clickedActionButton = Array.from(actionButtons).some(button => button.contains(e.target))
// 如果点击的不是主按钮和操作按钮,则收起
if (!clickedMainButton && !clickedActionButton) {
isExpanded.value = false
}
}
// 生命周期钩子 - 挂载时添加事件监听
onMounted(() => {
document.addEventListener('click', handleClickOutside)
})
// 生命周期钩子 - 卸载前清理事件监听
onBeforeUnmount(() => {
document.removeEventListener('click', handleClickOutside)
})
</script>
<style scoped>
.establish-container {
width: 150px;
position: fixed;
z-index: 1000;
bottom: 50px;
right: -10px;
}
/* 主圆形按钮样式 */
.main-button {
width: 30px;
height: 30px;
border-radius: 50%;
background-color: #409eff;
border: none;
color: white;
font-size: 24px;
cursor: pointer;
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
z-index: 1001;
margin-left: 80px;
}
.main-button:hover {
background-color: #66b1ff;
transform: scale(1.05);
}
.main-button.active {
background-color: #f56c6c;
transform: rotate(45deg);
}
.main-button.active:hover {
background-color: #f78989;
}
.main-button .icon {
font-style: normal;
transition: transform 0.3s ease;
}
/* 弹出的按钮样式 */
.action-button {
text-align: center;
display: flex;
align-items: center;
justify-content: flex-end;
padding: 10px 15px;
background-color: white;
border: 1px solid #dcdfe6;
border-radius: 20px;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
margin-bottom: 10px;
opacity: 0;
/* 从右侧滑入 */
min-width: 120px;
}
.action-button:hover span {
color: red;
}
.action-button i {
margin-right: 8px;
font-style: normal;
color: #409eff;
font-size: 16px;
}
.action-button span {
color: #606266;
font-size: 14px;
}
/* 确保按钮按顺序显示,形成卷帘效果 */
.action-button.show:nth-child(1) {
transition: all 0.9s ease-in-out;
margin-left: 0;
opacity: 1;
}
.action-button.show:nth-child(2) {
transition: all 0.7s ease-in-out;
margin-left: 0;
opacity: 1;
}
.action-button.show:nth-child(3) {
transition: all 0.5s ease-in-out;
margin-left: 0;
opacity: 1;
}
.action-button.show:nth-child(4) {
transition: all 0.3s ease-in-out;
margin-left: 0;
opacity: 1;
}
/* 确保按钮按顺序关闭,形成卷帘效果 */
.action-button:nth-child(1) {
transition: all 0.3s ease-in-out;
margin-left: 150px;
opacity: 1;
}
.action-button:nth-child(2) {
transition: all 0.5s ease-in-out;
margin-left: 150px;
opacity: 1;
}
.action-button:nth-child(3) {
transition: all 0.7s ease-in-out;
margin-left: 150px;
opacity: 1;
}
.action-button:nth-child(4) {
transition: all 0.9s ease-in-out;
margin-left: 150px;
opacity: 1;
}
/* 响应式调整 */
@media (max-width: 768px) {
.establish-container {
bottom: 20px;
right: 20px;
}
.main-button {
width: 50px;
height: 50px;
font-size: 20px;
bottom: 0;
right: 0;
}
.expanded-buttons.show {
width: 130px;
}
.action-button {
min-width: 100px;
padding: 8px 12px;
}
.action-button span {
font-size: 12px;
}
}
</style>

View File

@@ -12,6 +12,7 @@ import 'undraw-ui/dist/style.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import './styles/MainLayout.css'
// 创建Pinia实例
const pinia = createPinia()

View File

@@ -6,6 +6,8 @@ import NonsensePage from '../views/nonsense.vue'
import MessageBoardPage from '../views/messageboard.vue'
import AboutMePage from '../views/aboutme.vue'
import ArticleContentPage from '../views/articlecontents.vue'
import LoginPage from '../views/login.vue'
import ArticleSavePage from '../views/articlesave.vue'
/**
* 路由配置数组
@@ -65,12 +67,28 @@ const routes = [
}
},
{
path: '/article/:url',
path: '/article',
name: 'articleContent',
component: ArticleContentPage,
meta: {
title: '文章详情'
}
},
{
path: '/login',
name: 'login',
component: LoginPage,
meta: {
title: '登录'
}
},
{
path: '/articlesave',
name: 'articlesave',
component: ArticleSavePage,
meta: {
title: '保存文章'
}
}
]

View File

@@ -1,46 +1,75 @@
// 基础 API 服务配置
// 基础API服务
import axios from 'axios'
import { ElMessage } from 'element-plus'
// 创建axios实例
const apiService = axios.create({
baseURL: 'http://localhost:8080/api', // api的base_url
timeout: 10000, // 请求超时时间
headers: {
'Content-Type': 'application/json'
}
const api = axios.create({
baseURL:'http://localhost:8080/api', // API基础URL
timeout: 10000 // 请求超时时间
})
// 请求拦截器 - 添加认证token
apiService.interceptors.request.use(
// 请求拦截器
api.interceptors.request.use(
config => {
// 从localStorage获取token
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
// 设置Authorization头
config.headers['Authorization'] = `Bearer ${token}`
}
return config
},
error => {
// 请求错误处理
console.error('请求错误:', error)
return Promise.reject(error)
}
)
// 响应拦截器 - 统一处理响应
apiService.interceptors.response.use(
// 响应拦截器
api.interceptors.response.use(
response => {
// 检查响应是否成功
if (response.data && response.data.success) {
// 直接返回响应数据
return response.data
} else {
// 处理业务错误
return Promise.reject(new Error(response.data?.message || '请求失败'))
}
},
error => {
// 处理HTTP错误
console.error('API请求错误:', error)
// 可以在这里添加全局错误处理,如显示错误提示
// 错误处理
console.error('响应错误:', error)
let message = '请求失败'
if (error.response) {
// 服务器响应了但状态码不是2xx
switch (error.response.status) {
case 401:
message = '未授权,请重新登录'
// 可以在这里处理登出逻辑
localStorage.removeItem('token')
// 跳转到登录页
window.location.href = '/login'
break
case 403:
message = '拒绝访问'
break
case 404:
message = '请求资源不存在'
break
case 500:
message = '服务器错误'
break
default:
message = error.response.data?.message || message
}
} else if (error.request) {
// 请求已发出但没有收到响应
message = '网络错误,请检查网络连接'
}
// 使用Element Plus的消息组件显示错误信息
ElMessage.error(message)
return Promise.reject(error)
}
)
export default apiService
export default api

View File

@@ -1,119 +1,122 @@
// 文章相关API服务
import apiService from './apiService'
import api from './apiService'
/**
* 文章服务类
*/
class ArticleService {
/**
* 获取已发布文章
* @param {Object} params - 查询参数
* @returns {Promise}
* 获取已发布文章列表
* @param {import('../types').PaginationParams} params - 查询参数
* @returns {Promise<import('../types').ApiResponse<import('../types').Article[]>>}
*/
getAllArticles(params = {}) {
return apiService.get('/articles/published', { params })
return api.get('/articles/published', { params })
}
/**
* 获取单篇文章
* @param {number} id - 文章ID
* @returns {Promise}
* 根据ID获取文章详情
* @param {number} articleid - 文章ID
* @returns {Promise<import('../types').ApiResponse<import('../types').Article>>}
*/
getArticleById(id) {
return apiService.get(`/articles/${id}`)
getArticleById(articleid) {
return api.get(`/articles/${articleid}`)
}
/**
* 根据属性ID获取文章列表
* @param {number} attributeId - 属性ID
* @returns {Promise}
* @param {number} attributeid - 属性ID
* @returns {Promise<import('../types').ApiResponse<import('../types').Article[]>>}
*/
getArticlesByAttributeId(attributeId) {
return apiService.get(`/articles/attribute/${attributeId}`)
getArticlesByAttributeId(attributeid) {
return api.get(`/articles/attribute/${attributeid}`)
}
/**
* 根据标题查询文章列表
* @param {string} title - 文章标题
* @returns {Promise}
* @returns {Promise<import('../types').ApiResponse<import('../types').Article[]>>}
*/
getArticlesByTitle(title) {
return apiService.get(`/articles/title/${title}`)
return api.get(`/articles/title/${title}`)
}
/**
* 获取热门文章
* @returns {Promise}
* @returns {Promise<import('../types').ApiResponse<import('../types').Article[]>>}
*/
getPopularArticles() {
return apiService.get('/articles/popular')
return api.get('/articles/popular')
}
/**
* 创建文章
* @param {Object} articleData - 文章数据
* @returns {Promise}
* @param {import('../types').ArticleDto} articleData - 文章数据
* @returns {Promise<import('../types').ApiResponse<import('../types').Article>>}
*/
createArticle(articleData) {
return apiService.post('/articles', articleData)
createArticle(Article) {
return api.post('/articles', Article)
}
/**
* 更新文章
* @param {number} id - 文章ID
* @param {Object} articleData - 文章数据
* @returns {Promise}
* @param {number} articleid - 文章ID
* @param {import('../types').ArticleDto} articleData - 文章数据
* @returns {Promise<import('../types').ApiResponse<import('../types').Article>>}
*/
updateArticle(id, articleData) {
return apiService.put(`/articles/${id}`, articleData)
updateArticle(articleid, articleData) {
return api.put(`/articles/${articleid}`, articleData)
}
/**
* 删除文章
* @param {number} id - 文章ID
* @returns {Promise}
* @param {number} articleid - 文章ID
* @returns {Promise<import('../types').ApiResponse<boolean>>}
*/
deleteArticle(id) {
return apiService.delete(`/articles/${id}`)
deleteArticle(articleid) {
return api.delete(`/articles/${articleid}`)
}
/**
* 根据分类获取文章
* @param {number} categoryid - 分类ID
* @returns {Promise<import('../types').ApiResponse<import('../types').Article[]>>}
*/
getArticlesByCategory(categoryid) {
return api.get(`/articles/category/${categoryid}`)
}
/**
* 增加文章浏览量
* @param {number} id - 文章ID
* @returns {Promise}
* @param {number} articleid - 文章ID
* @returns {Promise<import('../types').ApiResponse<boolean>>}
*/
incrementArticleViews(id) {
return apiService.post(`/articles/view/${id}`)
}
/**
* 根据分类ID获取文章兼容旧接口
* @param {number} categoryId - 分类ID
* @returns {Promise}
*/
getArticlesByCategory(categoryId) {
return apiService.get(`/articles/category/${categoryId}`)
}
/**
* 根据属性ID获取文章
* @param {number} attributeId - 属性ID
* @returns {Promise}
*/
getArticlesByAttribute(attributeId) {
return apiService.get(`/articles/attribute/${attributeId}`)
incrementArticleViews(articleid) {
return api.post(`/articles/view/${articleid}`)
}
/**
* 根据属性ID获取最新文章
* @param {number} attributeId - 属性ID
* @returns {Promise}
* @param {number} attributeid - 属性ID
* @returns {Promise<import('../types').ApiResponse<import('../types').Article[]>>}
*/
getLatestArticlesByAttribute(attributeId) {
return apiService.get(`/articles/attribute/${attributeId}/latest`)
getLatestArticlesByAttribute(attributeid) {
return api.get(`/articles/attribute/${attributeid}/latest`)
}
/**
* 点赞文章
* @param {number} articleid - 文章ID
* @returns {Promise<import('../types').ApiResponse<boolean>>}
*/
likeArticle(articleid) {
return api.post(`/articles/like/${articleid}`)
}
}
// 导出文章服务实例
export default new ArticleService()
// 创建并导出服务实例
const articleService = new ArticleService()
export default articleService
// 导出服务类供特殊场景使用
export { ArticleService }

View File

@@ -1,5 +1,5 @@
// 分类属性相关API服务
import apiService from './apiService'
import api from './apiService'
/**
* 分类属性服务类
@@ -7,60 +7,64 @@ import apiService from './apiService'
class CategoryAttributeService {
/**
* 根据ID获取分类属性
* @param {number} id - 属性ID
* @returns {Promise}
* @param {number} attributeid - 属性ID
* @returns {Promise<import('../types').ApiResponse<import('../types').CategoryAttribute>>}
*/
getAttributeById(id) {
return apiService.get(`/category-attributes/${id}`)
getAttributeById(attributeid) {
return api.get(`/category-attributes/${attributeid}`)
}
/**
* 根据分类ID获取属性列表
* @param {number} categoryId - 分类ID
* @returns {Promise}
* @param {number} categoryid - 分类ID
* @returns {Promise<import('../types').ApiResponse<import('../types').CategoryAttribute[]>>}
*/
getAttributesByCategory(categoryId) {
return apiService.get(`/category-attributes/category/${categoryId}`)
getAttributesByCategory(categoryid) {
return api.get(`/category-attributes/category/${categoryid}`)
}
/**
* 创建分类属性
* @param {Object} attributeData - 属性数据
* @returns {Promise}
* @param {import('../types').CategoryAttributeDto} attributeData - 属性数据
* @returns {Promise<import('../types').ApiResponse<import('../types').CategoryAttribute>>}
*/
createAttribute(attributeData) {
return apiService.post('/category-attributes', attributeData)
return api.post('/category-attributes', attributeData)
}
/**
* 更新分类属性
* @param {number} id - 属性ID
* @param {Object} attributeData - 属性数据
* @returns {Promise}
* @param {number} attributeid - 属性ID
* @param {import('../types').CategoryAttributeDto} attributeData - 属性数据
* @returns {Promise<import('../types').ApiResponse<import('../types').CategoryAttribute>>}
*/
updateAttribute(id, attributeData) {
return apiService.put(`/category-attributes/${id}`, attributeData)
updateAttribute(attributeid, attributeData) {
return api.put(`/category-attributes/${attributeid}`, attributeData)
}
/**
* 删除分类属性
* @param {number} id - 属性ID
* @returns {Promise}
* @param {number} attributeid - 属性ID
* @returns {Promise<import('../types').ApiResponse<boolean>>}
*/
deleteAttribute(id) {
return apiService.delete(`/category-attributes/${id}`)
deleteAttribute(attributeid) {
return api.delete(`/category-attributes/${attributeid}`)
}
/**
* 检查分类下是否存在指定名称的属性
* @param {number} categoryId - 分类ID
* @param {string} attributeName - 属性名称
* @returns {Promise}
* @param {number} categoryid - 分类ID
* @param {string} attributename - 属性名称
* @returns {Promise<import('../types').ApiResponse<boolean>>}
*/
checkAttributeExists(categoryId, attributeName) {
return apiService.get(`/category-attributes/check-exists?categoryId=${categoryId}&attributeName=${encodeURIComponent(attributeName)}`)
checkAttributeExists(categoryid, attributename) {
return api.get(`/category-attributes/check-exists?categoryid=${categoryid}&attributename=${encodeURIComponent(attributename)}`)
}
}
// 导出分类属性服务实例
export default new CategoryAttributeService()
// 创建并导出服务实例
const categoryAttributeService = new CategoryAttributeService()
export default categoryAttributeService
// 导出服务类供特殊场景使用
export { CategoryAttributeService }

View File

@@ -1,4 +1,4 @@
import apiService from './apiService'
import api from './apiService'
/**
* 分类服务
@@ -6,49 +6,53 @@ import apiService from './apiService'
class CategoryService {
/**
* 获取所有分类
* @returns {Promise}
* @returns {Promise<import('../types').ApiResponse<import('../types').Category[]>>}
*/
getAllCategories() {
return apiService.get('/categories')
return api.get('/categories')
}
/**
* 获取指定分类
* @param {number} id - 分类ID
* @returns {Promise}
* @param {number} typeid - 分类ID
* @returns {Promise<import('../types').ApiResponse<import('../types').Category>>}
*/
getCategory(id) {
return apiService.get(`/categories/${id}`)
getCategory(typeid) {
return api.get(`/categories/${typeid}`)
}
/**
* 创建新分类
* @param {Object} categoryData - 分类数据
* @returns {Promise}
* @param {import('../types').CategoryDto} categoryData - 分类数据
* @returns {Promise<import('../types').ApiResponse<import('../types').Category>>}
*/
createCategory(categoryData) {
return apiService.post('/categories', categoryData)
return api.post('/categories', categoryData)
}
/**
* 更新分类
* @param {number} id - 分类ID
* @param {Object} categoryData - 分类数据
* @returns {Promise}
* @param {number} typeid - 分类ID
* @param {import('../types').CategoryDto} categoryData - 分类数据
* @returns {Promise<import('../types').ApiResponse<import('../types').Category>>}
*/
updateCategory(id, categoryData) {
return apiService.put(`/categories/${id}`, categoryData)
updateCategory(typeid, categoryData) {
return api.put(`/categories/${typeid}`, categoryData)
}
/**
* 删除分类
* @param {number} id - 分类ID
* @returns {Promise}
* @param {number} typeid - 分类ID
* @returns {Promise<import('../types').ApiResponse<boolean>>}
*/
deleteCategory(id) {
return apiService.delete(`/categories/${id}`)
deleteCategory(typeid) {
return api.delete(`/categories/${typeid}`)
}
}
// 导出分类服务实例
export default new CategoryService()
// 创建并导出服务实例
const categoryService = new CategoryService()
export default categoryService
// 导出服务类供特殊场景使用
export { CategoryService }

View File

@@ -1,17 +1,16 @@
// 导出所有服务
import articleService from './articleService'
import messageService from './messageService'
import categoryAttributeService from './categoryAttributeService'
import categoryService from './categoryService'
import categoryAttributeService from './categoryAttributeService'
import loginService from './loginService'
import messageService from './messageService'
// 导出服务类供特殊场景使用
export {
articleService,
messageService,
categoryService,
categoryAttributeService,
categoryService
loginService,
messageService
}

View File

@@ -0,0 +1,59 @@
// 登录相关API服务
import api from "./apiService";
/**
* 登录服务类
*/
class LoginService {
/**
* 用户登录
* @param {import('../types').LoginDto} loginData - 登录数据
* @returns {Promise<import('../types').ApiResponse<import('../types').User>>}
*/
login(loginData) {
return api.post("/auth/login", loginData);
}
/**
* 用户注册
* @param {import('../types').RegisterDto} registerData - 注册数据
* @returns {Promise<import('../types').ApiResponse<import('../types').User>>}
*/
register(registerData) {
return api.post("/auth/register", registerData);
}
/**
* 登出
*/
logout() {
return api.post("/auth/logout");
}
/**
* 获取当前用户信息
* @returns {Promise<import('../types').ApiResponse<import('../types').User>>}
*/
getCurrentUser() {
return api.get("/user/info");
}
/**
* 更新用户信息
* @param {import('../types').UserDto} userData - 用户数据
* @returns {Promise<import('../types').ApiResponse<import('../types').User>>}
*/
updateUser(userData) {
return api.put("/user/update", userData);
}
/**
* 修改密码
* @param {import('../types').ChangePasswordDto} passwordData - 密码数据
* @returns {Promise<import('../types').ApiResponse<boolean>>}
*/
changePassword(passwordData) {
return api.post("/user/change-password", passwordData);
}
}
export default new LoginService();

View File

@@ -7,7 +7,7 @@ import apiService from './apiService'
class MessageService {
/**
* 获取所有留言
* @returns {Promise}
* @returns {Promise<import('../types').ApiResponse<import('../types').Message[]>>}
*/
getAllMessages() {
return apiService.get('/messages')
@@ -15,25 +15,25 @@ class MessageService {
/**
* 获取单条留言
* @param {number} id - 留言ID
* @returns {Promise}
* @param {number} messageid - 留言ID
* @returns {Promise<import('../types').ApiResponse<import('../types').Message>>}
*/
getMessageById(id) {
return apiService.get(`/messages/${id}`)
getMessageById(messageid) {
return apiService.get(`/messages/${messageid}`)
}
/**
* 根据文章ID获取留言
* @param {number} articleId - 文章ID
* @returns {Promise}
* @param {number} articleid - 文章ID
* @returns {Promise<import('../types').ApiResponse<import('../types').Message[]>>}
*/
getMessagesByArticleId(articleId) {
return apiService.get(`/messages/article/${articleId}`)
getMessagesByArticleId(articleid) {
return apiService.get(`/messages/article/${articleid}`)
}
/**
* 获取根留言
* @returns {Promise}
* @returns {Promise<import('../types').ApiResponse<import('../types').Message[]>>}
*/
getRootMessages() {
return apiService.get('/messages/root')
@@ -41,17 +41,17 @@ class MessageService {
/**
* 根据父留言ID获取回复
* @param {number} parentId - 父留言ID
* @returns {Promise}
* @param {number} parentid - 父留言ID
* @returns {Promise<import('../types').ApiResponse<import('../types').Message[]>>}
*/
getRepliesByParentId(parentId) {
return apiService.get(`/messages/${parentId}/replies`)
getRepliesByParentId(parentid) {
return apiService.get(`/messages/${parentid}/replies`)
}
/**
* 根据昵称搜索留言
* @param {string} nickname - 昵称
* @returns {Promise}
* @returns {Promise<import('../types').ApiResponse<import('../types').Message[]>>}
*/
searchMessagesByNickname(nickname) {
return apiService.get(`/messages/search?nickname=${nickname}`)
@@ -59,17 +59,17 @@ class MessageService {
/**
* 获取文章评论数量
* @param {number} articleId - 文章ID
* @returns {Promise}
* @param {number} articleid - 文章ID
* @returns {Promise<import('../types').ApiResponse<number>>}
*/
getMessageCountByArticleId(articleId) {
return apiService.get(`/messages/count/article/${articleId}`)
getMessageCountByArticleId(articleid) {
return apiService.get(`/messages/count/article/${articleid}`)
}
/**
* 创建留言
* @param {Object} messageData - 留言数据
* @returns {Promise}
* @param {import('../types').MessageDto} messageData - 留言数据
* @returns {Promise<import('../types').ApiResponse<import('../types').Message>>}
*/
saveMessage(messageData) {
return apiService.post('/messages', messageData)
@@ -77,20 +77,21 @@ class MessageService {
/**
* 删除留言
* @param {number} id - 留言ID
* @returns {Promise}
* @param {number} messageid - 留言ID
* @returns {Promise<import('../types').ApiResponse<boolean>>}
*/
deleteMessage(id) {
return apiService.delete(`/messages/${id}`)
deleteMessage(messageid) {
return apiService.delete(`/messages/${messageid}`)
}
/**
* 点赞留言
* @param {number} id - 留言ID
* @returns {Promise}
* @param {number} messageid - 留言ID
* @returns {Promise<import('../types').ApiResponse<boolean>>}
*
*/
likeMessage(id) {
return apiService.post(`/messages/${id}/like`)
likeMessage(messageid) {
return apiService.post(`/messages/${messageid}/like`)
}
}

View File

@@ -1,3 +1,4 @@
import Login from '@/views/login.vue'
import { defineStore } from 'pinia'
/**
@@ -13,13 +14,17 @@ export const useGlobalStore = defineStore('global', {
const savedGlobalData = localStorage.getItem('globalStoreData')
const initialGlobalData = savedGlobalData ? JSON.parse(savedGlobalData) : {}
// 从localStorage读取特定状态的持久化数据
const savedSpecificData = localStorage.getItem('globalStoreSpecificData')
const initialSpecificData = savedSpecificData ? JSON.parse(savedSpecificData) : {}
return {
// 全局数据对象,存储所有需要共享的数据
globalData: initialGlobalData,
// 可以在这里定义特定状态属性,便于类型提示和直接使用
user: null,
loading: false,
notifications: []
// 特定状态属性,从localStorage读取初始值
user: initialSpecificData.user || null,
Login: initialSpecificData.Login || false,
notifications: initialSpecificData.notifications || []
}
},
@@ -71,7 +76,14 @@ export const useGlobalStore = defineStore('global', {
*/
_persistData() {
try {
// 持久化globalData
localStorage.setItem('globalStoreData', JSON.stringify(this.globalData))
// 持久化特定状态属性
localStorage.setItem('globalStoreSpecificData', JSON.stringify({
user: this.user,
Login: this.Login,
notifications: this.notifications
}))
} catch (error) {
console.error('Failed to persist data to localStorage:', error)
}
@@ -106,9 +118,13 @@ export const useGlobalStore = defineStore('global', {
*/
clearAll() {
this.globalData = {}
this.user = null
this.Login = false
this.notifications = []
// 清除localStorage中的数据
try {
localStorage.removeItem('globalStoreData')
// localStorage.removeItem('globalStoreSpecificData')
} catch (error) {
console.error('Failed to clear data from localStorage:', error)
}
@@ -120,6 +136,8 @@ export const useGlobalStore = defineStore('global', {
*/
setUser(userInfo) {
this.user = userInfo
// 持久化到localStorage
this._persistData()
},
/**
@@ -128,6 +146,8 @@ export const useGlobalStore = defineStore('global', {
*/
setLoading(status) {
this.loading = status
// 持久化到localStorage
this._persistData()
},
/**
@@ -139,6 +159,8 @@ export const useGlobalStore = defineStore('global', {
id: Date.now(),
...notification
})
// 持久化到localStorage
this._persistData()
},
/**
@@ -149,7 +171,19 @@ export const useGlobalStore = defineStore('global', {
const index = this.notifications.findIndex(notification => notification.id === id)
if (index !== -1) {
this.notifications.splice(index, 1)
// 持久化到localStorage
this._persistData()
}
},
/**
* 设置登录状态
* @param {boolean} status - 登录状态
*/
setLoginStatus(status) {
this.Login = status
// 持久化到localStorage
this._persistData()
}
}
})

View File

@@ -97,7 +97,6 @@ h6 {
font-family: inherit;
font-weight: 400;
line-height: 1.5;
margin-bottom: .5rem;
color: var(--font-color-title);
}
@@ -118,7 +117,6 @@ p {
z-index: 1000;
transition: all 1s ease;
font-size: inherit;
/* display: flex; */
justify-content: space-between;
align-items: center;
}

View File

@@ -4,48 +4,124 @@
* 文章类型接口
*/
export interface Article {
id: number
articleid: number
title: string
content: string
author: string
createTime: string
updateTime: string
// categoryId: number
attributeid?: number
categoryName?: string[]
tags?: string[]
attributeid: Number
categoryName: string
img?: string
createdAt: string
updatedAt: string
viewCount?: number
commentCount?: number
articleid?: string
publishedAt?: string
mg?: string
likes?: number
status?: number
markdownscontent: string
}
/**
* 文章DTO接口
*/
export interface ArticleDto {
id?: number
title: string
content: string
attributeid: number
img?: string
status?: number
}
/**
* 留言类型接口
*/
export interface Message {
id: number
messageid: number
content: string
nickname: string
email: string
articleId?: number
parentId?: number
articleid?: number
parentid?: number
createdAt: string
replyid?: number
time?: string
likes?: number
}
/**
* 留言DTO接口
*/
export interface MessageDto {
messageid?: number
nickname?: string
email?: string
content?: string
createdAt?: string
parentid?: number
replyid?: number
articleid?: number
}
/**
* 分类类型接口
*/
export interface Category {
id: number
name: string
typeid: number
typename: string
description?: string
createdAt?: string
updatedAt?: string
articleCount?: number
}
/**
* 分类DTO接口
*/
export interface CategoryDto {
typename: string
description?: string
}
/**
* 分类属性接口
*/
export interface CategoryAttribute {
attributeid: number
categoryid: number
attributename: string
}
/**
* 分类属性DTO接口
*/
export interface CategoryAttributeDto {
categoryid: number
attributename: string
}
/**
* 用户信息接口
*/
export interface User {
id?: number
username?: string
password?: string
email?: string
phone?: string
role?: number
createTime?: string
avatar?: string
token?: string
}
/**
* 用户DTO接口
*/
export interface UserDto {
username: string
password: string
email: string
phone: string
role?: number
}
/**
* API响应接口
*/
@@ -66,15 +142,3 @@ export interface PaginationParams {
keyword?: string
[key: string]: any
}
/**
* 用户信息接口
*/
export interface User {
id?: number
username?: string
email?: string
avatar?: string
role?: string
token?: string
}

View File

@@ -88,8 +88,7 @@ const goToMessageBoard = () => {
<style scoped>
/* 主容器样式 */
.about-page-container {
width: 100%;
padding: 20px 0;
width: 80%;
}
/* 内容包装器样式 */

View File

@@ -120,8 +120,7 @@ const getCategoryAttributes = async (processedCategories: any[]) => {
try {
// 使用正确的方法名获取属性下的文章优先使用typeid
const idToUse = attribute.typeid || attribute.categoryid;
const articlesRes = await articleService.getArticlesByAttribute(idToUse);
const articlesRes = await articleService.getArticlesByAttributeId(idToUse);
// 处理文章数据
attribute.articles = articlesRes.data && Array.isArray(articlesRes.data) ?
articlesRes.data : [];

View File

@@ -21,7 +21,7 @@
<div class="article-meta-info">
<span class="meta-item">
<i class="el-icon-date"></i>
{{ formatDate(article.createTime) }}
{{ formatDate(article.createdAt) }}
</span>
<span class="meta-item">
<i class="el-icon-folder"></i>
@@ -36,15 +36,15 @@
<!-- 文章内容区域 -->
<div class="article-content-area">
<div v-html="article.content"></div>
<markdownViewer :markdownContent="article.markdownscontent" />
</div>
<!-- 文章底部信息 -->
<div class="article-footer-section">
<div class="article-tag-list">
<span v-for="tag in article.tags || []" :key="tag" class="el-tag el-tag--primary">
<!-- <span v-for="tag in article.tags || []" :key="tag" class="el-tag el-tag--primary">
{{ tag }}
</span>
</span> -->
</div>
<!-- 文章操作按钮 -->
@@ -59,8 +59,8 @@
<div class="related-articles-section" v-if="relatedArticles.length > 0">
<h3>相关文章</h3>
<div class="related-articles-list">
<div v-for="item in relatedArticles" :key="item.id" class="related-article-card"
@click="handleRelatedArticleClick(item.id)">
<div v-for="item in relatedArticles" :key="item.articleid" class="related-article-card"
@click="handleRelatedArticleClick(item.articleid)">
<i class="el-icon-document"></i>
<span>{{ item.title }}</span>
</div>
@@ -75,7 +75,7 @@
<!-- 评论区组件 -->
<div>
<messageboard class="comment-section" v-if="article && Object.keys(article).length > 0" />
<messageboard class="comment-section" />
</div>
</div>
@@ -86,18 +86,18 @@
import { useRoute, useRouter } from 'vue-router'
import { ref, onMounted } from 'vue'
import { articleService } from '@/services'
import { messageService } from '@/services'
import { categoryAttributeService } from '@/services'
import { useGlobalStore } from '@/store/globalStore'
import { ElMessage } from 'element-plus'
import type { Article } from '@/types'
import { formatDate } from '@/utils/dateUtils'
import messageboard from './messageboard.vue'
import markdownViewer from './markdown.vue'
// 路由相关
const route = useRoute()
const router = useRouter()
// 响应式状态管理
const globalStore = useGlobalStore()
const article = ref<Article | null>(null) // 使用 null 作为初始值,避免类型不匹配问题
const loading = ref(false)
const error = ref('')
@@ -112,26 +112,22 @@ const fetchArticleDetail = async () => {
error.value = ''
// 获取路由参数
const articleId = route.query.url as string
console.log('获取文章ID:', articleId)
const articleId = globalStore.getValue('articleInfo')?.articleid || null
if (!articleId) {
throw new Error('文章ID不存在')
throw new Error('文章不存在')
}
// 获取文章详情
const response = await articleService.getArticleById(Number(articleId))
if (response.data) {
article.value = response.data
const response = await globalStore.getValue('articleInfo')
// const markdowndata = await
if (response) {
article.value = response
// 获取并设置分类名称
const categoryResponse = await categoryAttributeService.getAttributeById(article.value.attributeid)
const categoryResponse = await categoryAttributeService.getAttributeById(Number(article.value.attributeid))
article.value.categoryName = categoryResponse.data.attributename || '未分类'
// 增加文章浏览量
try {
await articleService.incrementArticleViews(Number(articleId))
console.log('文章浏览量增加成功')
// 更新前端显示的浏览量
if (article.value.viewCount) {
article.value.viewCount++
@@ -142,32 +138,15 @@ const fetchArticleDetail = async () => {
console.error('增加文章浏览量失败:', err)
// 不阻止主流程
}
// 获取相关文章(同属性下的其他文章)
if (article.value.attributeid) {
try {
const relatedResponse = await articleService.getArticlesByCategory(article.value.attributeid)
// 过滤掉当前文章并取前5篇作为相关文章
relatedArticles.value = relatedResponse.data
? relatedResponse.data.filter((item: Article) => item.id !== article.value?.id).slice(0, 5)
: []
} catch (err) {
console.error('获取相关文章失败:', err)
// 不阻止主流程
}
}
} else {
throw new Error('文章不存在或已被删除')
}
console.log('获取文章详情成功:', article.value)
} catch (err) {
error.value = err instanceof Error ? err.message : '获取文章详情失败,请稍后重试'
console.error('获取文章详情失败:', err)
ElMessage.error(error.value)
} finally {
loading.value = false
console.log('文章详情加载完成')
}
}
@@ -198,13 +177,6 @@ onMounted(() => {
</script>
<style scoped>
/* 页面主容器 */
#article-detail-page {
min-height: 100vh;
background-color: #f5f7fa;
padding: 40px 0;
}
/* 文章详情容器 */
.article-detail-wrapper {
max-width: 900px;

68
src/views/articlesave.vue Normal file
View File

@@ -0,0 +1,68 @@
<template>
<div id="allstyle">
<div class="article-header-section">
<h1 class="article-main-title">{{ Articleform.title }}11</h1>
<div class="article-meta-info">
<span class="meta-item">
<i class="el-icon-date"></i>
<!-- {{ formatDate(Articleform.createdAt) }} -->
22
</span>
<span class="meta-item">
<i class="el-icon-folder"></i>
{{ Articleform.categoryName || '未分类' }}33
</span>
<span class="meta-item">
<i class="el-icon-view"></i>
{{ Articleform.viewCount || 0 }} 阅读
</span>
</div>
</div>
<div>
<MdEditor v-model="Articleform.markdownscontent" htmlPreview preview={false} class="markdown-editor" @on-save="handleSave" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { MdEditor } from 'md-editor-v3';
import 'md-editor-v3/lib/style.css';
import { articleService } from '@/services';
import type { Article } from '@/types/index.ts';
const Articleform = ref<Article>({
articleid: 0,
title: '',
content: '',
attributeid: 0,
categoryName: '',
createdAt: '',
updatedAt: '',
markdownscontent: ''
})
const handleSave = (markdown) => {
console.log(Articleform.value);
Articleform.value.markdownscontent = markdown;
// 这里可以添加保存逻辑,比如发送到服务器
articleService.createArticle({
title: Articleform.value.title,
content: Articleform.value.content,
attributeid: Articleform.value.attributeid,
categoryName: Articleform.value.categoryName,
markdownscontent: Articleform.value.markdownscontent
}).then(res => {
if (res.code === 200) {
ElMessage.success('文章保存成功')
} else {
ElMessage.error(res.msg || '文章保存失败')
}
}).catch(err => {
ElMessage.error(err.message || '文章保存失败')
})
};
</script>
<style scoped>
</style>

View File

@@ -27,7 +27,6 @@
</div>
</div>
</transition-group>
<!-- 空状态 -->
<div v-if="!loading && articleList.length === 0" class="empty-state-container">
<el-empty description="暂无文章" />
@@ -38,11 +37,10 @@
<script setup>
import { useRouter, useRoute } from 'vue-router'
import { ref, onMounted } from 'vue'
import { articleService } from '@/services'
import { formatDate, formatRelativeTime } from '@/utils/dateUtils'
import { formatContentPreview } from '@/utils/stringUtils'
import { ElMessage } from 'element-plus'
import { messageService, categoryAttributeService } from '@/services'
import { articleService, messageService, categoryAttributeService } from '@/services'
import { useGlobalStore } from '@/store/globalStore'
// 全局状态管理
@@ -74,8 +72,8 @@ const fetchArticles = async () => {
response = await articleService.getArticlesByAttributeId(attributeData.id)
} else if (pathSegment === 'aericletitle') {
// 按标题搜索文章
const titleData = globalStore.getValue('title')
response = await articleService.getArticlesByTitle(titleData.title)
const titleData = globalStore.getValue('articleserarch')
response = await articleService.getArticlesByTitle(titleData.name)
} else {
// 获取所有文章
console.log('获取所有文章列表')
@@ -87,7 +85,7 @@ const fetchArticles = async () => {
try {
// 获取留言数量
const messageResponse = await messageService.getMessagesByArticleId(article.articleid)
console.log(`文章ID: ${article.articleid}, 分类ID: ${article.attributeid}`)
// console.log(`文章ID: ${article.articleid}, 分类ID: ${article.attributeid}`)
// 获取分类名称
const categoryResponse = await categoryAttributeService.getAttributeById(article.attributeid)
@@ -113,7 +111,7 @@ const fetchArticles = async () => {
}
// 更新文章列表
console.log(response.data)
// console.log(response.data)
articleList.value = response.data || []
} catch (error) {
console.error('获取文章列表失败:', error)
@@ -128,18 +126,13 @@ const fetchArticles = async () => {
* @param {Object} article - 文章对象
*/
const handleArticleClick = (article) => {
console.log('文章点击:', article)
// 存储文章信息到全局状态
globalStore.setValue('articleInfo', {
id: article.articleId,
name: article.title || '未命名文章',
})
globalStore.setValue('articleInfo', article)
// 跳转到文章详情页
router.push({
path: '/article/:url',
query: { url: article.articleId }
path: '/article',
})
}
@@ -205,7 +198,6 @@ onMounted(() => {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(0, 0, 0, 0.05);
}
/* 文章卡片悬停渐变效果 */
.article-card::before {
content: '';

552
src/views/login.vue Normal file
View File

@@ -0,0 +1,552 @@
<template>
<div class="login-container">
<div class="login-form-wrapper">
<!-- 登录表单卡片 -->
<div class="login-card">
<!-- 标题区域 -->
<div class="login-header">
<h2 class="login-title">欢迎回来</h2>
<p class="login-subtitle">请登录您的账号继续访问</p>
</div>
<!-- 表单区域 -->
<form @submit.prevent="handleLogin" class="login-form">
<!-- 用户名输入 -->
<div class="form-group">
<div class="input-wrapper">
<!-- <i class="el-icon-user input-icon"></i> -->
<input id="username" v-model="loginForm.username" type="text" placeholder="请输入用户名" class="form-input"
:class="{ 'input-error': loginForm.username }" @blur="validateField('username')" />
</div>
<span v-if="errors.username" class="error-message">{{ errors.username }}</span>
</div>
<!-- 密码输入 -->
<div class="form-group">
<div class="input-wrapper">
<!-- <i class="el-icon-lock input-icon"></i> -->
<input id="password" v-model="loginForm.password" :type="showPassword ? 'text' : 'password'"
placeholder="请输入密码" class="form-input" :class="{ 'input-error': loginForm.password }"
@blur="validateField('password')" />
<i :class="showPassword ? 'el-icon-view' : 'el-icon-view-off'" class="toggle-password-icon" @click="togglePassword"></i>
</div>
<span v-if="errors.password" class="error-message">{{ errors.password }}</span>
</div>
<!-- 记住我和忘记密码 -->
<div class="form-options">
<label class="remember-me">
<input type="checkbox" v-model="loginForm.rememberMe" class="remember-checkbox" />
<span>记住我</span>
</label>
<a href="#" class="forgot-password" @click.prevent="handleForgotPassword">忘记密码</a>
</div>
<!-- 登录按钮 -->
<button type="submit" class="login-button" :disabled="isLoading">
<span v-if="!isLoading">登录</span>
<span v-else class="loading-text">
<i class="el-icon-loading"></i> 登录中...
</span>
</button>
<!-- 其他登录方式 -->
<div class="other-login-methods">
<div class="divider">
<span>其他登录方式</span>
</div>
<div class="social-login-buttons">
<button type="button" class="social-button wechat" @click="handleSocialLogin('wechat')">
<i class="el-icon-chat-dot-square"></i>
</button>
<button type="button" class="social-button qq" @click="handleSocialLogin('qq')">
<i class="el-icon-message"></i>
</button>
<button type="button" class="social-button github" @click="handleSocialLogin('github')">
<i class="el-icon-document-checked"></i>
</button>
</div>
</div>
</form>
<!-- 注册提示 -->
<div class="register-prompt">
<span>还没有账号</span>
<router-link to="/register" class="register-link">立即注册</router-link>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { loginService } from '@/services'
import { ElMessage } from 'element-plus'
// 全局状态管理
import { useGlobalStore } from '@/store/globalStore'
const globalStore = useGlobalStore()
const router = useRouter()
const errors = ref({
username: null,
password: null,
})
const loginForm = reactive({
username: null,
password: null,
})
// 切换密码显示状态
const togglePassword = () => {
form.showPassword = !form.showPassword
}
// 验证单个字段
const validateField = (field) => {
console.log('validateField', field)
errors.value = {}
if (field === 'username' && !loginForm.username) {
errors.value.username = '请输入用户名'
}
if (field === 'password' && !loginForm.password) {
errors.value.password = '请输入密码'
} else if (field === 'password' && loginForm.password.length < 6) {
errors.value.password = '密码长度至少为6位'
}
}
// 验证整个表单
const validateForm = () => {
errors.value = {}
let isValid = true
if (!loginForm.username) {
errors.value.username = '请输入用户名'
isValid = false
}
if (!loginForm.password) {
errors.value.password = '请输入密码'
isValid = false
} else if (loginForm.password.length < 6) {
errors.value.password = '密码长度至少为6位'
isValid = false
}
return isValid;
}
// 处理登录
const handleLogin = async () => {
if (!validateForm()) {
return
}
try {
// 模拟登录请求
let user = await loginService.login(loginForm)
if (!user) {
ElMessage.error('登录失败,请检查用户名和密码')
return
}
// 这里应该是实际的登录API调用
// console.log('登录请求数据:', loginForm)
// 模拟登录成功
ElMessage.success('登录成功')
// 登录成功后,设置全局状态为已登录
globalStore.setLoginStatus(user.success)
globalStore.setLoading(user.success)
console.log('globalStore.Login', globalStore.Login)
// 保存登录状态
// if (loginForm.rememberMe) {
// localStorage.setItem('username', loginForm.username)
// }
// 跳转到首页
router.push('/')
} catch (error) {
ElMessage.error('登录失败,请检查用户名和密码')
console.error('登录错误:', error)
} finally {
globalStore.setLoading(false)
}
}
// 处理忘记密码
const handleForgotPassword = () => {
ElMessage.info('忘记密码功能开发中')
}
// 处理第三方登录
const handleSocialLogin = (provider) => {
ElMessage.info(`${provider} 登录功能开发中`)
}
// mounted() {
// // 从localStorage中恢复用户名
// const savedUsername = localStorage.getItem('username')
// if (savedUsername) {
// loginForm.username = savedUsername
// loginForm.rememberMe = true
// }
// }
</script>
<style scoped>
/* 登录页面容器 */
.login-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f7fa;
padding: 20px;
background-image: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
position: relative;
overflow: hidden;
}
/* 添加背景装饰 */
.login-container::before,
.login-container::after {
content: '';
position: absolute;
width: 300px;
height: 300px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
z-index: 0;
}
.login-container::before {
top: -100px;
right: -100px;
}
.login-container::after {
bottom: -100px;
left: -100px;
}
/* 登录表单包装器 */
.login-form-wrapper {
position: relative;
z-index: 1;
width: 100%;
max-width: 450px;
}
/* 登录卡片 */
.login-card {
background: white;
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
padding: 40px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.login-card:hover {
transform: translateY(-5px);
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15);
}
/* 登录头部 */
.login-header {
text-align: center;
margin-bottom: 30px;
}
.login-title {
font-size: 28px;
font-weight: 700;
color: #333;
margin-bottom: 10px;
background: linear-gradient(90deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.login-subtitle {
font-size: 16px;
color: #666;
margin: 0;
}
/* 表单样式 */
.login-form {
width: 100%;
}
.form-group {
margin-bottom: 24px;
position: relative;
}
.form-label {
display: block;
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
}
.input-wrapper {
position: relative;
display: flex;
align-items: center;
}
.form-input {
width: 100%;
height: 48px;
padding: 0 45px;
border: 2px solid #e1e5e9;
border-radius: 12px;
font-size: 16px;
transition: all 0.3s ease;
background-color: #fafbfc;
}
.form-input:focus {
outline: none;
border-color: #667eea;
background-color: white;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.form-input.input-error {
border-color: #f56565;
}
.input-icon {
position: absolute;
left: 15px;
color: #94a3b8;
font-size: 18px;
}
.toggle-password-icon {
position: absolute;
right: 15px;
color: #94a3b8;
font-size: 18px;
cursor: pointer;
transition: color 0.3s ease;
}
.toggle-password-icon:hover {
color: #667eea;
}
.error-message {
display: block;
font-size: 12px;
color: #f56565;
margin-top: 4px;
}
/* 表单选项 */
.form-options {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.remember-me {
display: flex;
align-items: center;
cursor: pointer;
user-select: none;
}
.remember-checkbox {
margin-right: 8px;
width: 16px;
height: 16px;
cursor: pointer;
}
.forgot-password {
font-size: 14px;
color: #667eea;
text-decoration: none;
transition: color 0.3s ease;
}
.forgot-password:hover {
color: #5a67d8;
text-decoration: underline;
}
/* 登录按钮 */
.login-button {
width: 100%;
height: 48px;
background: linear-gradient(90deg, #667eea, #764ba2);
color: white;
border: none;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.login-button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
}
.login-button:active:not(:disabled) {
transform: translateY(0);
}
.login-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.loading-text {
display: flex;
align-items: center;
}
.loading-text i {
margin-right: 8px;
}
/* 其他登录方式 */
.other-login-methods {
margin-top: 30px;
}
.divider {
position: relative;
text-align: center;
margin-bottom: 20px;
}
.divider span {
position: relative;
background: white;
padding: 0 20px;
color: #666;
font-size: 14px;
}
.divider::before {
content: '';
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 1px;
background: #e1e5e9;
z-index: 0;
}
.social-login-buttons {
display: flex;
justify-content: center;
gap: 16px;
}
.social-button {
width: 44px;
height: 44px;
border: 2px solid #e1e5e9;
border-radius: 50%;
background: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
transition: all 0.3s ease;
}
.social-button:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.social-button.wechat:hover {
border-color: #07c160;
color: #07c160;
}
.social-button.qq:hover {
border-color: #1da1f2;
color: #1da1f2;
}
.social-button.github:hover {
border-color: #333;
color: #333;
}
/* 注册提示 */
.register-prompt {
text-align: center;
margin-top: 30px;
font-size: 14px;
color: #666;
}
.register-link {
color: #667eea;
text-decoration: none;
font-weight: 500;
transition: color 0.3s ease;
}
.register-link:hover {
color: #5a67d8;
text-decoration: underline;
}
/* 响应式设计 */
@media (max-width: 768px) {
.login-container {
padding: 10px;
}
.login-card {
padding: 30px 20px;
border-radius: 16px;
}
.login-title {
font-size: 24px;
}
.form-input {
height: 44px;
font-size: 15px;
}
.login-button {
height: 44px;
}
}
@media (max-width: 480px) {
.login-card {
padding: 25px 15px;
}
.social-login-buttons {
gap: 12px;
}
.social-button {
width: 40px;
height: 40px;
font-size: 18px;
}
}
</style>

139
src/views/markdown.vue Normal file
View File

@@ -0,0 +1,139 @@
<template>
<div class="markdown-viewer">
<div v-if="loading" class="loading-state">加载中...</div>
<div v-else-if="error" class="error-state">加载失败: {{ error }}</div>
<v-md-editor
v-else
class="editor"
v-model="content"
mode="preview"
:preview-theme="theme"
:height="height"
/>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
// 导入v-md-editor相关组件和样式
import VMdEditor from '@kangc/v-md-editor'
import '@kangc/v-md-editor/lib/style/base-editor.css'
// 导入主题使用github主题
import githubTheme from '@kangc/v-md-editor/lib/theme/github.js'
import '@kangc/v-md-editor/lib/theme/style/github.css'
// 导入代码高亮
import hljs from 'highlight.js'
import 'highlight.js/styles/github.css'
// 配置主题
VMdEditor.use(githubTheme, {
Hljs: hljs
})
// 定义组件属性
const props = defineProps({
// 从父组件传入的markdownid内容
markdownContent: {
type: String,
default: ''
},
// 指定API端点
apiEndpoint: {
type: String,
default: '/help'
},
// 编辑器高度
height: {
type: String,
default: 'auto'
},
// 主题配置
theme: {
type: String,
default: 'github'
}
})
// 响应式数据
const content = ref('')
const loading = ref(false)
const error = ref('')
// 判断内容是否为HTML格式
// 从API获取markdown内容
const loadMarkdown = async () => {
// 如果父组件提供了markdown内容直接使用
loading.value = true
error.value = ''
try {
content.value = props.markdownContent
} catch (err) {
error.value = err instanceof Error ? err.message : '未知错误'
console.error('加载markdown失败:', err)
} finally {
loading.value = false
}
}
// 组件挂载时加载markdown
onMounted(() => {
loadMarkdown()
})
</script>
<style scoped>
.markdown-viewer {
text-align: left;
width: 100%;
height: 100%;
}
.editor {
border: 1px solid #e0e0e0;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
transition: box-shadow 0.3s ease;
}
.editor:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
/* 加载状态样式 */
.loading-state {
display: flex;
justify-content: center;
align-items: center;
padding: 60px 20px;
color: #606266;
font-size: 14px;
background-color: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
/* 错误状态样式 */
.error-state {
padding: 40px 20px;
background-color: #fef0f0;
border: 1px solid #fbc4c4;
border-radius: 8px;
color: #f56c6c;
font-size: 14px;
text-align: center;
}
/* 确保编辑器内容在移动设备上正常显示 */
@media (max-width: 768px) {
.editor {
border-radius: 4px;
}
.loading-state,
.error-state {
padding: 30px 15px;
font-size: 13px;
}
}
</style>

View File

@@ -45,6 +45,8 @@
👍
</span>
<span class="reply-button" @click="handleReply(comment, reply)">回复</span>
<!-- 删除按钮 -->
<span class="delete-button" @click="handleDelete(reply.messageid)" v-if=" globalStore.Login">删除</span>
</div>
</div>
</div>
@@ -241,9 +243,9 @@ const fetchMessages = async () => {
let articleid = props.comments || null
if (!articleid) {
// 安全获取文章ID如果globalStore中没有articlebutn则返回null
const articleData = globalStore.getValue('articlebutn')
articleid = (articleData && typeof articleData === 'object' && 'id' in articleData) ? articleData.id : null
// 安全获取文章ID如果globalStore中没有articleInfo则返回null
const articleData = globalStore.getValue('articleInfo')
articleid = (articleData && typeof articleData === 'object' && 'articleid' in articleData) ? articleData.articleid : null
}
form.articleid = articleid
@@ -251,7 +253,6 @@ const fetchMessages = async () => {
// 根据是否有文章ID选择不同的API调用
if (articleid) {
res = await messageService.getMessagesByArticleId(articleid)
console.log(`获取文章ID=${articleid}的相关留言列表`)
} else {
res = await messageService.getAllMessages()
// 过滤掉articleid不为空的留言只保留articleid为空或不存在的留言
@@ -460,7 +461,30 @@ const handleLike = async (msg) => {
msg.isLiking = false
}
}
// 处理删除
const handleDelete = async (msg) => {
try {
// 显示加载状态或禁用按钮
// msg.isDeleting = true
if (!confirm('确定删除吗?')) {
return
}
const res = await messageService.deleteMessage(msg.messageid)
if (res.success) {
// 从列表中移除
messageBoardData.value = messageBoardData.value.filter(item => item.messageid !== msg.messageid)
ElMessage.success('删除成功')
} else {
ElMessage.error('删除失败:' + (res.message || '未知错误'))
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error('网络错误,请稍后重试')
} finally {
// msg.isDeleting = false
}
}
</script>
<style scoped lang="scss">
@@ -603,7 +627,18 @@ const handleLike = async (msg) => {
color: #409eff;
background-color: rgba(64, 158, 255, 0.1);
}
// 删除按钮
.delete-button {
cursor: pointer;
transition: color 0.3s ease;
padding: 4px 8px;
border-radius: 4px;
display: inline-block;
}
.delete-button:hover {
color: #e74c3c;
background-color: rgba(231, 76, 60, 0.1);
}
/* 回复列表容器 */
.reply-list-container {
margin-top: 16px;