feat: 添加登录功能与文章编辑功能
refactor: 重构API服务与全局状态管理 style: 优化UI样式与布局 fix: 修复文章列表与详情页的显示问题 docs: 更新类型定义与注释 chore: 更新依赖包与配置文件
This commit is contained in:
@@ -1,6 +1,4 @@
|
||||
{
|
||||
|
||||
|
||||
"include": ["src/**/*"],
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
|
||||
822
package-lock.json
generated
822
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
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 });
|
||||
};
|
||||
|
||||
@@ -170,9 +172,12 @@ 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`});
|
||||
}
|
||||
|
||||
// 搜索后保持搜索框打开状态
|
||||
@@ -192,9 +197,16 @@ const updatePageState = (url: string) => {
|
||||
/**
|
||||
* 设置当前激活的菜单项
|
||||
*/
|
||||
const setActiveIndex = (path: string) => {
|
||||
console.log('设置激活索引:', path);
|
||||
activeIndex.value =path;
|
||||
const setActiveIndex = (path: string) => {
|
||||
// 存储文章信息到全局状态
|
||||
globalStore.setValue('localpath', {
|
||||
name: path
|
||||
})
|
||||
// console.log('设置激活索引:', path);
|
||||
if (path === 'message') {
|
||||
globalStore.removeValue('articleInfo')
|
||||
}
|
||||
activeIndex.value =path;
|
||||
};
|
||||
|
||||
|
||||
|
||||
293
src/layouts/establish.vue
Normal file
293
src/layouts/establish.vue
Normal 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>
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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: '保存文章'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
// 创建axios实例
|
||||
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 || '请求失败'))
|
||||
}
|
||||
// 直接返回响应数据
|
||||
return response.data
|
||||
},
|
||||
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
|
||||
@@ -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 }
|
||||
@@ -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 }
|
||||
@@ -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 }
|
||||
@@ -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
|
||||
}
|
||||
59
src/services/loginService.js
Normal file
59
src/services/loginService.js
Normal 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();
|
||||
@@ -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`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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响应接口
|
||||
*/
|
||||
@@ -65,16 +141,4 @@ export interface PaginationParams {
|
||||
size?: number
|
||||
keyword?: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户信息接口
|
||||
*/
|
||||
export interface User {
|
||||
id?: number
|
||||
username?: string
|
||||
email?: string
|
||||
avatar?: string
|
||||
role?: string
|
||||
token?: string
|
||||
}
|
||||
@@ -88,8 +88,7 @@ const goToMessageBoard = () => {
|
||||
<style scoped>
|
||||
/* 主容器样式 */
|
||||
.about-page-container {
|
||||
width: 100%;
|
||||
padding: 20px 0;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
/* 内容包装器样式 */
|
||||
|
||||
@@ -68,7 +68,7 @@ const fetchCategories = async () => {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
|
||||
const res = await categoryService.getAllCategories();
|
||||
const res = await categoryService.getAllCategories();
|
||||
|
||||
if (res.data && res.data.length > 0) {
|
||||
// 创建处理后的分类数组
|
||||
@@ -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 : [];
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
<div id="article-detail-page">
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-state-container">
|
||||
<el-skeleton :count="1" />
|
||||
<el-skeleton :count="3" />
|
||||
<el-skeleton :count="1" />
|
||||
<el-skeleton :count="3" />
|
||||
</div>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<div v-else-if="error" class="error-state-container">
|
||||
<el-alert :title="error" type="error" show-icon />
|
||||
<el-alert :title="error" type="error" show-icon />
|
||||
<el-button type="primary" @click="fetchArticleDetail">重新加载</el-button>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
|
||||
<!-- 文章操作按钮 -->
|
||||
@@ -54,13 +54,13 @@
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 相关文章推荐 -->
|
||||
<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
68
src/views/articlesave.vue
Normal 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>
|
||||
@@ -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
552
src/views/login.vue
Normal 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
139
src/views/markdown.vue
Normal 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>
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user