style: 完善练习提交页面
382
package-lock.json
generated
@ -13,8 +13,10 @@
|
|||||||
"ckplayer": "^3.1.2",
|
"ckplayer": "^3.1.2",
|
||||||
"naive-ui": "^2.42.0",
|
"naive-ui": "^2.42.0",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
|
"quill": "^2.0.3",
|
||||||
"vue": "^3.5.17",
|
"vue": "^3.5.17",
|
||||||
"vue-i18n": "^9.14.5",
|
"vue-i18n": "^9.14.5",
|
||||||
|
"vue-quill-editor": "^3.0.6",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -1833,6 +1835,24 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/call-bind": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.0",
|
||||||
|
"es-define-property": "^1.0.0",
|
||||||
|
"get-intrinsic": "^1.2.4",
|
||||||
|
"set-function-length": "^1.2.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/call-bind-apply-helpers": {
|
"node_modules/call-bind-apply-helpers": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
@ -1846,6 +1866,22 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/call-bound": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
|
"get-intrinsic": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001727",
|
"version": "1.0.30001727",
|
||||||
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
|
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
|
||||||
@ -1873,6 +1909,15 @@
|
|||||||
"integrity": "sha512-JHlWTSRm6aqZx+dYdsa6MWz7151omcGBBF9EKK49NL1WCJ2olbdkt7CPKZHvW4lLVgxxEopmuLYEqNdb2cOPhA==",
|
"integrity": "sha512-JHlWTSRm6aqZx+dYdsa6MWz7151omcGBBF9EKK49NL1WCJ2olbdkt7CPKZHvW4lLVgxxEopmuLYEqNdb2cOPhA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/clone": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/clone/-/clone-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/combined-stream": {
|
"node_modules/combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
@ -1988,6 +2033,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/deep-equal": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/deep-equal/-/deep-equal-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-arguments": "^1.1.1",
|
||||||
|
"is-date-object": "^1.0.5",
|
||||||
|
"is-regex": "^1.1.4",
|
||||||
|
"object-is": "^1.1.5",
|
||||||
|
"object-keys": "^1.1.1",
|
||||||
|
"regexp.prototype.flags": "^1.5.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/default-browser": {
|
"node_modules/default-browser": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmmirror.com/default-browser/-/default-browser-5.2.1.tgz",
|
"resolved": "https://registry.npmmirror.com/default-browser/-/default-browser-5.2.1.tgz",
|
||||||
@ -2018,6 +2083,23 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/define-data-property": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-define-property": "^1.0.0",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"gopd": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/define-lazy-prop": {
|
"node_modules/define-lazy-prop": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
|
||||||
@ -2031,6 +2113,23 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/define-properties": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"define-data-property": "^1.0.1",
|
||||||
|
"has-property-descriptors": "^1.0.0",
|
||||||
|
"object-keys": "^1.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/delayed-stream": {
|
"node_modules/delayed-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
@ -2186,6 +2285,12 @@
|
|||||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/eventemitter3": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/evtd": {
|
"node_modules/evtd": {
|
||||||
"version": "0.2.4",
|
"version": "0.2.4",
|
||||||
"resolved": "https://registry.npmmirror.com/evtd/-/evtd-0.2.4.tgz",
|
"resolved": "https://registry.npmmirror.com/evtd/-/evtd-0.2.4.tgz",
|
||||||
@ -2219,6 +2324,18 @@
|
|||||||
"url": "https://github.com/sindresorhus/execa?sponsor=1"
|
"url": "https://github.com/sindresorhus/execa?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/extend": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/fast-diff": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/fdir": {
|
"node_modules/fdir": {
|
||||||
"version": "6.4.6",
|
"version": "6.4.6",
|
||||||
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.4.6.tgz",
|
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.4.6.tgz",
|
||||||
@ -2325,6 +2442,15 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/functions-have-names": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gensync": {
|
"node_modules/gensync": {
|
||||||
"version": "1.0.0-beta.2",
|
"version": "1.0.0-beta.2",
|
||||||
"resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz",
|
"resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||||
@ -2408,6 +2534,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/has-property-descriptors": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-define-property": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/has-symbols": {
|
"node_modules/has-symbols": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
@ -2482,6 +2620,38 @@
|
|||||||
"node": ">=18.18.0"
|
"node": ">=18.18.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-arguments": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bound": "^1.0.2",
|
||||||
|
"has-tostringtag": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-date-object": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bound": "^1.0.2",
|
||||||
|
"has-tostringtag": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-docker": {
|
"node_modules/is-docker": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/is-docker/-/is-docker-3.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/is-docker/-/is-docker-3.0.0.tgz",
|
||||||
@ -2530,6 +2700,24 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-regex": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bound": "^1.0.2",
|
||||||
|
"gopd": "^1.2.0",
|
||||||
|
"has-tostringtag": "^1.0.2",
|
||||||
|
"hasown": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-stream": {
|
"node_modules/is-stream": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-4.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-4.0.1.tgz",
|
||||||
@ -2656,6 +2844,19 @@
|
|||||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.clonedeep": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isequal": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
||||||
|
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lru-cache": {
|
"node_modules/lru-cache": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz",
|
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||||
@ -2820,6 +3021,40 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/object-assign": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/object-is": {
|
||||||
|
"version": "1.1.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/object-is/-/object-is-1.1.6.tgz",
|
||||||
|
"integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind": "^1.0.7",
|
||||||
|
"define-properties": "^1.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/object-keys": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/open": {
|
"node_modules/open": {
|
||||||
"version": "10.2.0",
|
"version": "10.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/open/-/open-10.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/open/-/open-10.2.0.tgz",
|
||||||
@ -2839,6 +3074,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/parchment": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/parchment/-/parchment-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/parse-ms": {
|
"node_modules/parse-ms": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/parse-ms/-/parse-ms-4.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/parse-ms/-/parse-ms-4.0.0.tgz",
|
||||||
@ -2972,6 +3213,55 @@
|
|||||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/quill": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/quill/-/quill-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"eventemitter3": "^5.0.1",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"parchment": "^3.0.0",
|
||||||
|
"quill-delta": "^5.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"npm": ">=8.2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/quill-delta": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/quill-delta/-/quill-delta-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-diff": "^1.3.0",
|
||||||
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
"lodash.isequal": "^4.5.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/regexp.prototype.flags": {
|
||||||
|
"version": "1.5.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
|
||||||
|
"integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind": "^1.0.8",
|
||||||
|
"define-properties": "^1.2.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-proto": "^1.0.1",
|
||||||
|
"gopd": "^1.2.0",
|
||||||
|
"set-function-name": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/rfdc": {
|
"node_modules/rfdc": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz",
|
"resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz",
|
||||||
@ -3047,6 +3337,38 @@
|
|||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/set-function-length": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"define-data-property": "^1.1.4",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"get-intrinsic": "^1.2.4",
|
||||||
|
"gopd": "^1.0.1",
|
||||||
|
"has-property-descriptors": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/set-function-name": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"define-data-property": "^1.1.4",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"functions-have-names": "^1.2.3",
|
||||||
|
"has-property-descriptors": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
@ -3490,6 +3812,66 @@
|
|||||||
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-quill-editor": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/vue-quill-editor/-/vue-quill-editor-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-g20oSZNWg8Hbu41Kinjd55e235qVWPLfg4NvsLW6d+DhgBTFbEuMpcWlUdrD6qT3+Noim6DRu18VLM9lVShXOQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"quill": "^1.3.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4.0.0",
|
||||||
|
"npm": ">= 3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vue-quill-editor/node_modules/eventemitter3": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/vue-quill-editor/node_modules/fast-diff": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/vue-quill-editor/node_modules/parchment": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/parchment/-/parchment-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/vue-quill-editor/node_modules/quill": {
|
||||||
|
"version": "1.3.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/quill/-/quill-1.3.7.tgz",
|
||||||
|
"integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"clone": "^2.1.1",
|
||||||
|
"deep-equal": "^1.0.1",
|
||||||
|
"eventemitter3": "^2.0.3",
|
||||||
|
"extend": "^3.0.2",
|
||||||
|
"parchment": "^1.1.4",
|
||||||
|
"quill-delta": "^3.6.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vue-quill-editor/node_modules/quill-delta": {
|
||||||
|
"version": "3.6.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/quill-delta/-/quill-delta-3.6.3.tgz",
|
||||||
|
"integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"deep-equal": "^1.0.1",
|
||||||
|
"extend": "^3.0.2",
|
||||||
|
"fast-diff": "1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vue-router": {
|
"node_modules/vue-router": {
|
||||||
"version": "4.5.1",
|
"version": "4.5.1",
|
||||||
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz",
|
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz",
|
||||||
|
@ -18,8 +18,10 @@
|
|||||||
"ckplayer": "^3.1.2",
|
"ckplayer": "^3.1.2",
|
||||||
"naive-ui": "^2.42.0",
|
"naive-ui": "^2.42.0",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
|
"quill": "^2.0.3",
|
||||||
"vue": "^3.5.17",
|
"vue": "^3.5.17",
|
||||||
"vue-i18n": "^9.14.5",
|
"vue-i18n": "^9.14.5",
|
||||||
|
"vue-quill-editor": "^3.0.6",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
BIN
public/images/aiCompanion/homework.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
public/images/aiCompanion/reply.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
public/images/aiCompanion/talk.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
public/images/courses/@.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
public/images/courses/Image.png
Normal file
After Width: | Height: | Size: 858 B |
BIN
public/images/courses/comments-note-active.png
Normal file
After Width: | Height: | Size: 594 B |
BIN
public/images/courses/comments-note.png
Normal file
After Width: | Height: | Size: 547 B |
BIN
public/images/courses/expression.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
public/images/examination/score-bg.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
BIN
public/images/profile/del-black.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
public/images/profile/file.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
480
src/components/common/QuillEditor.vue
Normal file
@ -0,0 +1,480 @@
|
|||||||
|
<template>
|
||||||
|
<div class="quill-editor-container">
|
||||||
|
<div ref="editorContainer" class="editor-content"></div>
|
||||||
|
|
||||||
|
<!-- 文件列表区域 -->
|
||||||
|
<div class="file-list-section" v-if="uploadedFiles.length > 0">
|
||||||
|
<div class="file-list">
|
||||||
|
<div v-for="(file, index) in uploadedFiles" :key="index" class="file-item">
|
||||||
|
<div class="file-icon">
|
||||||
|
<img src="/images/profile/file.png" alt="文件图标" />
|
||||||
|
</div>
|
||||||
|
<div class="file-info">
|
||||||
|
<div class="file-name">{{ file.name }}</div>
|
||||||
|
<div class="file-size">{{ file.size }}</div>
|
||||||
|
</div>
|
||||||
|
<button class="file-delete" @click="removeFile(index)" title="删除文件">
|
||||||
|
<img src="/images/profile/del-black.png" alt="删除" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="showColorPicker" class="color-picker-popup" @click.stop>
|
||||||
|
<div class="color-grid">
|
||||||
|
<div v-for="color in colors" :key="color" class="color-item" :style="{ backgroundColor: color }" @click="selectColor(color)"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
||||||
|
import Quill from 'quill'
|
||||||
|
import 'quill/dist/quill.snow.css'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue?: string
|
||||||
|
placeholder?: string
|
||||||
|
height?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
modelValue: '',
|
||||||
|
placeholder: '请输入内容...',
|
||||||
|
height: '500px'
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:modelValue': [value: string]
|
||||||
|
'change': [value: string]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const editorContainer = ref<HTMLElement>()
|
||||||
|
const quill = ref<Quill>()
|
||||||
|
const fontSize = ref(14)
|
||||||
|
const isBold = ref(false)
|
||||||
|
const isList = ref(false)
|
||||||
|
const showColorPicker = ref(false)
|
||||||
|
const uploadedFiles = ref<Array<{name: string, size: string}>>([])
|
||||||
|
|
||||||
|
const colors = [
|
||||||
|
'#000000', '#FF0000', '#00FF00', '#0000FF', '#FFFF00',
|
||||||
|
'#FF00FF', '#00FFFF', '#FFA500', '#800080', '#008000',
|
||||||
|
'#FFC0CB', '#A52A2A', '#808080', '#C0C0C0', '#FFFFFF'
|
||||||
|
]
|
||||||
|
|
||||||
|
const initQuill = () => {
|
||||||
|
if (!editorContainer.value) return
|
||||||
|
|
||||||
|
const toolbarOptions = [
|
||||||
|
['bold', 'underline', 'italic'],
|
||||||
|
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
|
||||||
|
[{ 'color': [] }, { 'background': [] }],
|
||||||
|
[{ 'size': ['12', '14', '16', '18'] }],
|
||||||
|
['image'],
|
||||||
|
['clean']
|
||||||
|
]
|
||||||
|
|
||||||
|
quill.value = new Quill(editorContainer.value, {
|
||||||
|
theme: 'snow',
|
||||||
|
modules: {
|
||||||
|
toolbar: {
|
||||||
|
container: toolbarOptions,
|
||||||
|
handlers: {
|
||||||
|
image: () => insertImage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
placeholder: props.placeholder
|
||||||
|
})
|
||||||
|
|
||||||
|
// 添加自定义文件上传按钮
|
||||||
|
addCustomFileButton()
|
||||||
|
|
||||||
|
if (props.modelValue) {
|
||||||
|
quill.value.root.innerHTML = props.modelValue
|
||||||
|
}
|
||||||
|
|
||||||
|
quill.value.on('text-change', () => {
|
||||||
|
const content = quill.value?.root.innerHTML || ''
|
||||||
|
emit('update:modelValue', content)
|
||||||
|
emit('change', content)
|
||||||
|
})
|
||||||
|
|
||||||
|
quill.value.on('selection-change', (range) => {
|
||||||
|
if (range) {
|
||||||
|
const formats = quill.value?.getFormat(range)
|
||||||
|
isBold.value = !!formats?.bold
|
||||||
|
isList.value = !!formats?.list
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeFontSize = () => {
|
||||||
|
if (quill.value) {
|
||||||
|
quill.value.format('size', fontSize.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleBold = () => {
|
||||||
|
if (quill.value) {
|
||||||
|
quill.value.format('bold', !isBold.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleFontStyle = () => {
|
||||||
|
if (quill.value) {
|
||||||
|
quill.value.format('italic', !quill.value.getFormat().italic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleColorPicker = () => {
|
||||||
|
showColorPicker.value = !showColorPicker.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectColor = (color: string) => {
|
||||||
|
if (quill.value) {
|
||||||
|
quill.value.format('color', color)
|
||||||
|
}
|
||||||
|
showColorPicker.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleList = () => {
|
||||||
|
if (quill.value) {
|
||||||
|
const format = quill.value.getFormat()
|
||||||
|
if (format.list) {
|
||||||
|
quill.value.format('list', false)
|
||||||
|
} else {
|
||||||
|
quill.value.format('list', 'bullet')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertImage = () => {
|
||||||
|
const input = document.createElement('input')
|
||||||
|
input.setAttribute('type', 'file')
|
||||||
|
input.setAttribute('accept', 'image/*')
|
||||||
|
input.click()
|
||||||
|
|
||||||
|
input.onchange = () => {
|
||||||
|
const file = input.files?.[0]
|
||||||
|
if (file && quill.value) {
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = (e) => {
|
||||||
|
const range = quill.value?.getSelection()
|
||||||
|
if (range) {
|
||||||
|
quill.value?.insertEmbed(range.index, 'image', e.target?.result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertFile = () => {
|
||||||
|
const input = document.createElement('input')
|
||||||
|
input.setAttribute('type', 'file')
|
||||||
|
input.setAttribute('accept', '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt')
|
||||||
|
input.click()
|
||||||
|
|
||||||
|
input.onchange = () => {
|
||||||
|
const file = input.files?.[0]
|
||||||
|
if (file) {
|
||||||
|
const fileSize = (file.size / 1024).toFixed(2) + 'KB'
|
||||||
|
uploadedFiles.value.push({
|
||||||
|
name: file.name,
|
||||||
|
size: fileSize
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeFile = (index: number) => {
|
||||||
|
uploadedFiles.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addCustomFileButton = () => {
|
||||||
|
if (!quill.value) return
|
||||||
|
|
||||||
|
// 获取工具栏
|
||||||
|
const toolbar = quill.value.getModule('toolbar')
|
||||||
|
const toolbarElement = toolbar.container
|
||||||
|
|
||||||
|
// 查找最后一个格式组
|
||||||
|
const lastFormatGroup = toolbarElement.querySelector('.ql-formats:last-child')
|
||||||
|
|
||||||
|
if (lastFormatGroup) {
|
||||||
|
// 创建文件按钮
|
||||||
|
const fileButton = document.createElement('button')
|
||||||
|
fileButton.type = 'button'
|
||||||
|
fileButton.className = 'ql-file'
|
||||||
|
fileButton.setAttribute('aria-label', 'file')
|
||||||
|
fileButton.setAttribute('title', '上传文件')
|
||||||
|
fileButton.innerHTML = `
|
||||||
|
<svg viewBox="0 0 18 18">
|
||||||
|
<path class="ql-stroke" d="M6,4V2A1,1,0,0,1,7,1H11A1,1,0,0,1,12,2V4"></path>
|
||||||
|
<path class="ql-stroke" d="M6,4H4A1,1,0,0,0,3,5V15A1,1,0,0,0,4,16H14A1,1,0,0,0,15,15V5A1,1,0,0,0,14,4H12"></path>
|
||||||
|
<line class="ql-stroke" x1="6" x2="12" y1="8" y2="8"></line>
|
||||||
|
<line class="ql-stroke" x1="6" x2="12" y1="11" y2="11"></line>
|
||||||
|
<line class="ql-stroke" x1="6" x2="10" y1="14" y2="14"></line>
|
||||||
|
</svg>
|
||||||
|
`
|
||||||
|
|
||||||
|
// 添加点击事件
|
||||||
|
fileButton.addEventListener('click', insertFile)
|
||||||
|
|
||||||
|
// 将按钮添加到工具栏
|
||||||
|
lastFormatGroup.appendChild(fileButton)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (newValue) => {
|
||||||
|
if (quill.value && newValue !== quill.value.root.innerHTML) {
|
||||||
|
quill.value.root.innerHTML = newValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
initQuill()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
getContent: () => quill.value?.root.innerHTML || '',
|
||||||
|
setContent: (content: string) => {
|
||||||
|
if (quill.value) {
|
||||||
|
quill.value.root.innerHTML = content
|
||||||
|
}
|
||||||
|
},
|
||||||
|
focus: () => quill.value?.focus()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.quill-editor-container {
|
||||||
|
border: 1px solid #F7F7F7;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-toolbar {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-bottom: 1px solid #F7F7F7;
|
||||||
|
padding: 8px 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-size-select {
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
background: white;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
min-width: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn:hover {
|
||||||
|
background: #e9ecef;
|
||||||
|
border-color: #adb5bd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn.active {
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
border-color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn.bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn.font-style {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn.color {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn.color::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 2px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 12px;
|
||||||
|
height: 2px;
|
||||||
|
background: #ff0000;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-content {
|
||||||
|
min-height: v-bind(height);
|
||||||
|
background: white;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-content :deep(.ql-editor) {
|
||||||
|
min-height: v-bind(height);
|
||||||
|
padding: 16px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-content :deep(.ql-toolbar) {
|
||||||
|
display: block;
|
||||||
|
background: white;
|
||||||
|
border-bottom: 1px solid #E6E6E6;
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-content :deep(.ql-container) {
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-content :deep(.ql-editor) {
|
||||||
|
min-height: v-bind(height);
|
||||||
|
padding: 16px;
|
||||||
|
padding-bottom: 80px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker-popup {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-item {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
transition: transform 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-item:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文件列表样式 */
|
||||||
|
.file-list-section {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 10;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: #F5F8FB;
|
||||||
|
padding: 8px 12px;
|
||||||
|
width: 187px;
|
||||||
|
min-height: 84px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-icon img {
|
||||||
|
width: 49px;
|
||||||
|
height: 49px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #000;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-size {
|
||||||
|
font-size: 8px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-delete {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-delete img {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-delete:hover img {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
@ -314,6 +314,22 @@ router.beforeEach((to, from, next) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 特殊处理:检测从其他页面进入已兑换页面
|
||||||
|
if (to.name === 'CourseExchanged') {
|
||||||
|
// 如果是从其他页面进入已兑换页面,设置自动刷新标记
|
||||||
|
if (from.name && from.name !== 'CourseExchanged') {
|
||||||
|
sessionStorage.setItem('refreshCourseExchanged', 'true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 特殊处理:检测从其他页面进入个人中心
|
||||||
|
if (to.name === 'Profile') {
|
||||||
|
// 如果是从其他页面进入个人中心,设置自动刷新标记
|
||||||
|
if (from.name && from.name !== 'Profile') {
|
||||||
|
sessionStorage.setItem('refreshProfile', 'true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
next()
|
next()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@
|
|||||||
<div class="course-description">
|
<div class="course-description">
|
||||||
<p>{{ course.description ||
|
<p>{{ course.description ||
|
||||||
'本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。课程内容与全国计算机等级考试、"1+X"WPS办公应用职业技能等级证书,技能大赛紧密结合,课程设置紧密对应实际全面共享,可为职业工作人员、在校学生、创行教师提供服务与学习支持。'
|
'本课程深度聚焦问题,让每一位教师了解并学习使用DeepSeek,结合办公自动化职业岗位标准,以实际工作任务为引导,强调课程内容的易用性和岗位要求的匹配性。课程内容与全国计算机等级考试、"1+X"WPS办公应用职业技能等级证书,技能大赛紧密结合,课程设置紧密对应实际全面共享,可为职业工作人员、在校学生、创行教师提供服务与学习支持。'
|
||||||
}}</p>
|
}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 讲师信息 -->
|
<!-- 讲师信息 -->
|
||||||
@ -190,14 +190,36 @@
|
|||||||
<!-- 评论内容 -->
|
<!-- 评论内容 -->
|
||||||
<div v-if="activeTab === 'comments'" class="tab-pane">
|
<div v-if="activeTab === 'comments'" class="tab-pane">
|
||||||
<div class="comments-content">
|
<div class="comments-content">
|
||||||
<!-- <div class="comment-stats">
|
<!-- 发布评论区域 -->
|
||||||
<span class="total-comments">共1251条评论</span>
|
<div class="post-comment-section">
|
||||||
<div class="comment-filters">
|
<div class="comment-input-wrapper">
|
||||||
<button class="filter-btn active">全部</button>
|
<div class="user-avatar">
|
||||||
<button class="filter-btn">最新</button>
|
<img src="/images/activity/6.png" alt="用户头像" />
|
||||||
<button class="filter-btn">最热</button>
|
</div>
|
||||||
|
<div class="comment-input-area">
|
||||||
|
<textarea v-model="newComment" placeholder="写下你的评论..." class="comment-textarea"
|
||||||
|
@input="adjustTextareaHeight" @click="handleTextareaClick"></textarea>
|
||||||
|
<div class="comment-toolbar">
|
||||||
|
<div class="toolbar-left">
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/expression.png" alt="表情" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/@.png" alt="@用户" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/Image.png" alt="图片" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="toolbar-right">
|
||||||
|
<button class="btn-submit" @click="submitComment">
|
||||||
|
发布
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div>
|
||||||
|
|
||||||
<div class="comment-list">
|
<div class="comment-list">
|
||||||
<div class="comment-item" v-for="comment in displayComments" :key="comment.id">
|
<div class="comment-item" v-for="comment in displayComments" :key="comment.id">
|
||||||
@ -207,23 +229,161 @@
|
|||||||
<div class="comment-content">
|
<div class="comment-content">
|
||||||
<div class="comment-header">
|
<div class="comment-header">
|
||||||
<span class="comment-username">{{ comment.username }}</span>
|
<span class="comment-username">{{ comment.username }}</span>
|
||||||
<!-- <span class="comment-time">{{ comment.time }}</span> -->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="comment-text">{{ comment.content }}</div>
|
<div class="comment-text">{{ comment.content }}</div>
|
||||||
<div class="comment-actions">
|
<div class="comment-actions">
|
||||||
<button class="action-btn">
|
<button class="action-btn">
|
||||||
<span class="top">置顶评论</span>
|
|
||||||
<span>2025.07.23 16:28</span>
|
<span>2025.07.23 16:28</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="action-btn">回复</button>
|
<button v-if="comment.type === 'note'" class="action-btn">
|
||||||
|
<span class="top">置顶评论</span>
|
||||||
|
</button>
|
||||||
|
<button v-else class="action-btn"
|
||||||
|
@click="startReply(comment.id, comment.username)">回复</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 回复输入区域 -->
|
||||||
|
<div v-if="replyingTo === comment.id" class="reply-input-section">
|
||||||
|
<div class="reply-input-header">
|
||||||
|
<span class="reply-to-text">回复 @{{ replyToUsername }}</span>
|
||||||
|
<button class="cancel-reply-btn" @click="cancelReply">取消</button>
|
||||||
|
</div>
|
||||||
|
<div class="reply-input-container">
|
||||||
|
<textarea v-model="replyText" placeholder="写下你的回复..." class="reply-textarea"
|
||||||
|
@input="adjustReplyTextareaHeight" @click="handleReplyTextareaClick"></textarea>
|
||||||
|
<div class="reply-toolbar">
|
||||||
|
<div class="toolbar-left">
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/expression.png" alt="表情" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/@.png" alt="@用户" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/Image.png" alt="图片" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="toolbar-right">
|
||||||
|
<button class="btn-submit" @click="submitReply" :disabled="!replyText.trim()">
|
||||||
|
发布
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 回复区域示例 -->
|
||||||
|
<div class="comment-replies" v-if="comment.id === 1">
|
||||||
|
<!-- 讲师回复 -->
|
||||||
|
<div class="reply-item instructor-reply">
|
||||||
|
<div class="reply-avatar">
|
||||||
|
<img src="/images/activity/6.png" alt="讲师头像" />
|
||||||
|
</div>
|
||||||
|
<div class="reply-content">
|
||||||
|
<div class="reply-main">
|
||||||
|
<div class="reply-header">
|
||||||
|
<span class="reply-username">张老师</span>
|
||||||
|
<span class="reply-badge instructor">讲师</span>
|
||||||
|
</div>
|
||||||
|
<div class="reply-text">感谢您的反馈!我们会继续优化课程内容,让学习体验更好。</div>
|
||||||
|
</div>
|
||||||
|
<div class="reply-footer">
|
||||||
|
<span class="reply-time">2025.07.23 17:30</span>
|
||||||
|
<div class="reply-actions">
|
||||||
|
<button class="reply-action-btn" @click="startReply(1, '张老师')">回复</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 用户回复 -->
|
||||||
|
<div class="reply-item user-reply">
|
||||||
|
<div class="reply-avatar">
|
||||||
|
<img src="/images/activity/7.png" alt="用户头像" />
|
||||||
|
</div>
|
||||||
|
<div class="reply-content">
|
||||||
|
<div class="reply-main">
|
||||||
|
<div class="reply-header">
|
||||||
|
<span class="reply-username">李同学</span>
|
||||||
|
<span class="reply-badge user">学员</span>
|
||||||
|
</div>
|
||||||
|
<div class="reply-text">同意楼上的观点,这个课程确实很有帮助!</div>
|
||||||
|
</div>
|
||||||
|
<div class="reply-footer">
|
||||||
|
<span class="reply-time">2025.07.23 18:15</span>
|
||||||
|
<div class="reply-actions">
|
||||||
|
<button class="reply-action-btn" @click="startReply(1, '李同学')">回复</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="comment-item">
|
||||||
|
<div class="comment-avatar">
|
||||||
|
<img
|
||||||
|
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80"
|
||||||
|
alt="张老师" />
|
||||||
|
</div>
|
||||||
|
<div class="comment-content">
|
||||||
|
<div class="comment-header">
|
||||||
|
<span class="comment-username">张老师</span>
|
||||||
|
</div>
|
||||||
|
<div class="comment-text">这个课程内容很实用,讲解得很清楚,对初学者很有帮助!111</div>
|
||||||
|
<div class="comment-image-container">
|
||||||
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
||||||
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
||||||
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
||||||
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
||||||
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
||||||
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
||||||
|
<div class="image-overlay">
|
||||||
|
<span class="more-images-text">+6</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="comment-actions">
|
||||||
|
<div class="note-icon-container">
|
||||||
|
<img src="/images/courses/comments-note.png" alt="笔记" class="note-icon">
|
||||||
|
<span>笔记</span>
|
||||||
|
</div>
|
||||||
|
<span class="comment-time">2025.07.23 16:28</span>
|
||||||
|
<button class="action-btn" @click="startReply(1, '张老师')">回复</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 回复输入区域 -->
|
||||||
|
<div v-if="replyingTo === 1" class="reply-input-section">
|
||||||
|
<div class="reply-input-header">
|
||||||
|
<span class="reply-to-text">回复 @{{ replyToUsername }}</span>
|
||||||
|
<button class="cancel-reply-btn" @click="cancelReply">取消</button>
|
||||||
|
</div>
|
||||||
|
<div class="reply-input-container">
|
||||||
|
<textarea v-model="replyText" placeholder="写下你的回复..." class="reply-textarea"
|
||||||
|
@input="adjustReplyTextareaHeight" @click="handleReplyTextareaClick"></textarea>
|
||||||
|
<div class="reply-toolbar">
|
||||||
|
<div class="toolbar-left">
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/expression.png" alt="表情" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/@.png" alt="@用户" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/Image.png" alt="图片" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="toolbar-right">
|
||||||
|
<button class="btn-submit" @click="submitReply" :disabled="!replyText.trim()">
|
||||||
|
发布
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div class="load-more">
|
|
||||||
<button class="btn-load-more">加载更多评论</button>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -811,7 +971,8 @@ const displayComments = ref([
|
|||||||
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
|
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
|
||||||
time: '2天前',
|
time: '2天前',
|
||||||
content: '老师讲得很详细,从零基础到实际应用都有涉及,非常适合初学者!',
|
content: '老师讲得很详细,从零基础到实际应用都有涉及,非常适合初学者!',
|
||||||
likes: 23
|
likes: 23,
|
||||||
|
type: 'comment'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
@ -819,7 +980,8 @@ const displayComments = ref([
|
|||||||
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
|
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
|
||||||
time: '5天前',
|
time: '5天前',
|
||||||
content: '课程内容很实用,跟着做了几个项目,收获很大。推荐给想学AI的朋友们!',
|
content: '课程内容很实用,跟着做了几个项目,收获很大。推荐给想学AI的朋友们!',
|
||||||
likes: 18
|
likes: 18,
|
||||||
|
type: 'comment'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
@ -827,10 +989,102 @@ const displayComments = ref([
|
|||||||
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
|
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
|
||||||
time: '1周前',
|
time: '1周前',
|
||||||
content: 'DeepSeek确实是个很强大的工具,通过这个课程学会了很多实用技巧。',
|
content: 'DeepSeek确实是个很强大的工具,通过这个课程学会了很多实用技巧。',
|
||||||
likes: 31
|
likes: 31,
|
||||||
|
type: 'comment'
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// 新评论内容
|
||||||
|
const newComment = ref('')
|
||||||
|
|
||||||
|
// 自动调整textarea高度
|
||||||
|
const adjustTextareaHeight = (event: Event) => {
|
||||||
|
const textarea = event.target as HTMLTextAreaElement
|
||||||
|
textarea.style.height = 'auto'
|
||||||
|
textarea.style.height = textarea.scrollHeight + 'px'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击textarea时调整高度
|
||||||
|
const handleTextareaClick = (event: MouseEvent) => {
|
||||||
|
const textarea = event.target as HTMLTextAreaElement
|
||||||
|
// 如果当前高度是40px,则调整到60px
|
||||||
|
if (textarea.style.height === '40px' || textarea.style.height === '') {
|
||||||
|
textarea.style.height = '60px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交评论函数
|
||||||
|
const submitComment = () => {
|
||||||
|
if (newComment.value.trim()) {
|
||||||
|
const newCommentObj = {
|
||||||
|
id: Date.now(),
|
||||||
|
username: '当前用户',
|
||||||
|
avatar: 'https://via.placeholder.com/40x40/1890ff/ffffff?text=我',
|
||||||
|
time: '刚刚',
|
||||||
|
content: newComment.value,
|
||||||
|
likes: 0,
|
||||||
|
type: 'comment'
|
||||||
|
}
|
||||||
|
displayComments.value.unshift(newCommentObj)
|
||||||
|
newComment.value = ''
|
||||||
|
// 这里可以调用API提交评论
|
||||||
|
console.log('评论已提交:', newCommentObj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回复相关函数
|
||||||
|
const startReply = (commentId: number, username: string) => {
|
||||||
|
replyingTo.value = commentId
|
||||||
|
replyToUsername.value = username
|
||||||
|
replyText.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelReply = () => {
|
||||||
|
replyingTo.value = null
|
||||||
|
replyToUsername.value = ''
|
||||||
|
replyText.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitReply = () => {
|
||||||
|
if (replyText.value.trim() && replyingTo.value) {
|
||||||
|
const newReplyObj = {
|
||||||
|
id: Date.now(),
|
||||||
|
username: '当前用户',
|
||||||
|
avatar: 'https://via.placeholder.com/40x40/1890ff/ffffff?text=我',
|
||||||
|
time: '刚刚',
|
||||||
|
content: replyText.value,
|
||||||
|
likes: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这里可以调用API提交回复
|
||||||
|
console.log('回复已提交:', newReplyObj)
|
||||||
|
console.log('回复给评论ID:', replyingTo.value)
|
||||||
|
console.log('回复给用户:', replyToUsername.value)
|
||||||
|
|
||||||
|
// 清空回复状态
|
||||||
|
cancelReply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回复文本框高度调整
|
||||||
|
const adjustReplyTextareaHeight = (event: Event) => {
|
||||||
|
const textarea = event.target as HTMLTextAreaElement
|
||||||
|
textarea.style.height = '40px'
|
||||||
|
textarea.style.height = textarea.scrollHeight + 'px'
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReplyTextareaClick = (event: MouseEvent) => {
|
||||||
|
const textarea = event.target as HTMLTextAreaElement
|
||||||
|
if (textarea.style.height === '40px' || !textarea.style.height) {
|
||||||
|
textarea.style.height = '60px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回复相关数据
|
||||||
|
const replyingTo = ref<number | null>(null)
|
||||||
|
const replyText = ref('')
|
||||||
|
const replyToUsername = ref('')
|
||||||
|
|
||||||
// 加载课程详情
|
// 加载课程详情
|
||||||
const loadCourseDetail = async () => {
|
const loadCourseDetail = async () => {
|
||||||
console.log('开始加载课程详情,课程ID:', courseId.value)
|
console.log('开始加载课程详情,课程ID:', courseId.value)
|
||||||
@ -3821,7 +4075,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
.ai-companion-tag {
|
.ai-companion-tag {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@ -3867,4 +4121,440 @@ height: 20px;
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 评论区样式 */
|
||||||
|
.comments-content {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 发布评论区域 */
|
||||||
|
.post-comment-section {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-input-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-input-area {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-textarea {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid #E6E6E6;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
resize: none;
|
||||||
|
font-family: inherit;
|
||||||
|
height: 40px;
|
||||||
|
min-height: 40px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: height 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #1890ff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-textarea::placeholder {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-left {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
width: 24px;
|
||||||
|
height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1.5px solid #E6E6E6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-icon {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit {
|
||||||
|
background: #0088D1;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 3px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit:hover:not(:disabled) {
|
||||||
|
background: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #bfbfbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 回复输入区域样式 */
|
||||||
|
.reply-input-section {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-input-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-to-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-reply-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-reply-btn:hover {
|
||||||
|
background: #f0f0f0;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-input-container {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #E6E6E6;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-textarea {
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
resize: none;
|
||||||
|
font-family: inherit;
|
||||||
|
height: 40px;
|
||||||
|
min-height: 40px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: height 0.3s ease;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #1890ff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-textarea::placeholder {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-avatar img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-username {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-time {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-text {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-image-container {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-image-container img {
|
||||||
|
width: 84px;
|
||||||
|
height: 84px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: calc(6 * (84px + 16px));
|
||||||
|
width: 84px;
|
||||||
|
height: 84px;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-images-text {
|
||||||
|
color: white;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-icon-container {
|
||||||
|
width: 50px;
|
||||||
|
height: 20px;
|
||||||
|
background: #EDEDED;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-icon-container img {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-icon-container span {
|
||||||
|
color: #666666;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn:hover {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn span {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn .top {
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #FF304B;
|
||||||
|
background-color: #FFF4F4;
|
||||||
|
border-radius: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 回复和二级评论样式 */
|
||||||
|
.comment-replies {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-avatar img {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-username {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-badge {
|
||||||
|
width: 32px;
|
||||||
|
height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-badge.instructor {
|
||||||
|
background: #EEF9FF;
|
||||||
|
color: #008BD7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-badge.user {
|
||||||
|
background: #f6ffed;
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-time {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-text {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #333;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 8px;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-action-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-action-btn:hover {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
@ -185,6 +185,37 @@
|
|||||||
<!-- 评论内容 -->
|
<!-- 评论内容 -->
|
||||||
<div v-if="activeTab === 'comments'" class="tab-pane">
|
<div v-if="activeTab === 'comments'" class="tab-pane">
|
||||||
<div class="comments-content">
|
<div class="comments-content">
|
||||||
|
<!-- 发布评论区域 -->
|
||||||
|
<div class="post-comment-section">
|
||||||
|
<div class="comment-input-wrapper">
|
||||||
|
<div class="user-avatar">
|
||||||
|
<img src="/images/activity/5.png" alt="用户头像" />
|
||||||
|
</div>
|
||||||
|
<div class="comment-input-area">
|
||||||
|
<textarea v-model="newComment" placeholder="写下你的评论..." rows="3" class="comment-textarea"
|
||||||
|
@input="adjustTextareaHeight" @click="handleTextareaClick"></textarea>
|
||||||
|
<div class="comment-toolbar">
|
||||||
|
<div class="toolbar-left">
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/expression.png" alt="表情" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/@.png" alt="@用户" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/Image.png" alt="图片" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="toolbar-right">
|
||||||
|
<button class="btn-submit" @click="submitComment">
|
||||||
|
发布
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- <div class="comment-stats">
|
<!-- <div class="comment-stats">
|
||||||
<span class="total-comments">共1251条评论</span>
|
<span class="total-comments">共1251条评论</span>
|
||||||
<div class="comment-filters">
|
<div class="comment-filters">
|
||||||
@ -210,7 +241,147 @@
|
|||||||
<span class="top">置顶评论</span>
|
<span class="top">置顶评论</span>
|
||||||
<span>2025.07.23 16:28</span>
|
<span>2025.07.23 16:28</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="action-btn">回复</button>
|
<button class="action-btn" @click="startReply(comment.id, comment.username)">回复</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 回复输入区域 -->
|
||||||
|
<div v-if="replyingTo === comment.id" class="reply-input-section">
|
||||||
|
<div class="reply-input-header">
|
||||||
|
<span class="reply-to-text">回复 @{{ replyToUsername }}</span>
|
||||||
|
<button class="cancel-reply-btn" @click="cancelReply">取消</button>
|
||||||
|
</div>
|
||||||
|
<div class="reply-input-container">
|
||||||
|
<textarea v-model="replyText" placeholder="写下你的回复..." class="reply-textarea"
|
||||||
|
@input="adjustReplyTextareaHeight" @click="handleReplyTextareaClick"></textarea>
|
||||||
|
<div class="reply-toolbar">
|
||||||
|
<div class="toolbar-left">
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/expression.png" alt="表情" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/@.png" alt="@用户" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/Image.png" alt="图片" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="toolbar-right">
|
||||||
|
<button class="btn-submit" @click="submitReply" :disabled="!replyText.trim()">
|
||||||
|
发布
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 回复区域示例 -->
|
||||||
|
<div class="comment-replies" v-if="comment.id === 1">
|
||||||
|
<!-- 讲师回复 -->
|
||||||
|
<div class="reply-item instructor-reply">
|
||||||
|
<div class="reply-avatar">
|
||||||
|
<img src="/images/activity/6.png" alt="讲师头像" />
|
||||||
|
</div>
|
||||||
|
<div class="reply-content">
|
||||||
|
<div class="reply-main">
|
||||||
|
<div class="reply-header">
|
||||||
|
<span class="reply-username">张老师</span>
|
||||||
|
<span class="reply-badge instructor">讲师</span>
|
||||||
|
</div>
|
||||||
|
<div class="reply-text">感谢您的反馈!我们会继续优化课程内容,让学习体验更好。</div>
|
||||||
|
</div>
|
||||||
|
<div class="reply-footer">
|
||||||
|
<span class="reply-time">2025.07.23 17:30</span>
|
||||||
|
<div class="reply-actions">
|
||||||
|
<button class="reply-action-btn" @click="startReply(1, '张老师')">回复</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 用户回复 -->
|
||||||
|
<div class="reply-item user-reply">
|
||||||
|
<div class="reply-avatar">
|
||||||
|
<img src="/images/activity/7.png" alt="用户头像" />
|
||||||
|
</div>
|
||||||
|
<div class="reply-content">
|
||||||
|
<div class="reply-main">
|
||||||
|
<div class="reply-header">
|
||||||
|
<span class="reply-username">李同学</span>
|
||||||
|
<span class="reply-badge user">学员</span>
|
||||||
|
</div>
|
||||||
|
<div class="reply-text">同意楼上的观点,这个课程确实很有帮助!</div>
|
||||||
|
</div>
|
||||||
|
<div class="reply-footer">
|
||||||
|
<span class="reply-time">2025.07.23 18:15</span>
|
||||||
|
<div class="reply-actions">
|
||||||
|
<button class="reply-action-btn" @click="startReply(1, '李同学')">回复</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="comment-item">
|
||||||
|
<div class="comment-avatar">
|
||||||
|
<img
|
||||||
|
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80"
|
||||||
|
alt="张老师" />
|
||||||
|
</div>
|
||||||
|
<div class="comment-content">
|
||||||
|
<div class="comment-header">
|
||||||
|
<span class="comment-username">张老师</span>
|
||||||
|
</div>
|
||||||
|
<div class="comment-text">这个课程内容很实用,讲解得很清楚,对初学者很有帮助!111</div>
|
||||||
|
<div class="comment-image-container">
|
||||||
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
||||||
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
||||||
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
||||||
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
||||||
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
||||||
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
||||||
|
<div class="image-overlay">
|
||||||
|
<span class="more-images-text">+6</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="comment-actions">
|
||||||
|
<div class="note-icon-container">
|
||||||
|
<img src="/images/courses/comments-note.png" alt="笔记" class="note-icon">
|
||||||
|
<span>笔记</span>
|
||||||
|
</div>
|
||||||
|
<span class="comment-time">2025.07.23 16:28</span>
|
||||||
|
<button class="action-btn" @click="startReply(1, '张老师')">回复</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 回复输入区域 -->
|
||||||
|
<div v-if="replyingTo === 1" class="reply-input-section">
|
||||||
|
<div class="reply-input-header">
|
||||||
|
<span class="reply-to-text">回复 @{{ replyToUsername }}</span>
|
||||||
|
<button class="cancel-reply-btn" @click="cancelReply">取消</button>
|
||||||
|
</div>
|
||||||
|
<div class="reply-input-container">
|
||||||
|
<textarea v-model="replyText" placeholder="写下你的回复..." class="reply-textarea"
|
||||||
|
@input="adjustReplyTextareaHeight" @click="handleReplyTextareaClick"></textarea>
|
||||||
|
<div class="reply-toolbar">
|
||||||
|
<div class="toolbar-left">
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/expression.png" alt="表情" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/@.png" alt="@用户" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/Image.png" alt="图片" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="toolbar-right">
|
||||||
|
<button class="btn-submit" @click="submitReply" :disabled="!replyText.trim()">
|
||||||
|
发布
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -289,7 +460,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="lesson-info">
|
<div class="lesson-info">
|
||||||
<span class="lesson-title" :class="{ 'disabled': !isUserEnrolled }">{{ section.name
|
<span class="lesson-title" :class="{ 'disabled': !isUserEnrolled }">{{ section.name
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="lesson-meta">
|
<div class="lesson-meta">
|
||||||
<span v-if="isVideoLesson(section)" class="lesson-duration"
|
<span v-if="isVideoLesson(section)" class="lesson-duration"
|
||||||
@ -563,7 +734,7 @@ const groupSectionsByChapter = (sections: CourseSection[]) => {
|
|||||||
const chapterSections = sections.slice(sectionIndex, sectionIndex + sectionsPerChapter[i])
|
const chapterSections = sections.slice(sectionIndex, sectionIndex + sectionsPerChapter[i])
|
||||||
if (chapterSections.length > 0) {
|
if (chapterSections.length > 0) {
|
||||||
groups.push({
|
groups.push({
|
||||||
title: `第${i+1}章 ${chapterTitles[i]}`,
|
title: `第${i + 1}章 ${chapterTitles[i]}`,
|
||||||
sections: chapterSections,
|
sections: chapterSections,
|
||||||
expanded: i === 0 // 默认展开第一章
|
expanded: i === 0 // 默认展开第一章
|
||||||
})
|
})
|
||||||
@ -646,7 +817,7 @@ const totalSections = computed(() => {
|
|||||||
const formatTotalDuration = () => {
|
const formatTotalDuration = () => {
|
||||||
// 计算总时长
|
// 计算总时长
|
||||||
let totalMinutes = 0
|
let totalMinutes = 0
|
||||||
courseSections.value.forEach(section => {
|
courseSections.value.forEach((section: CourseSection) => {
|
||||||
if (section.duration) {
|
if (section.duration) {
|
||||||
const parts = section.duration.split(':')
|
const parts = section.duration.split(':')
|
||||||
if (parts.length === 3) {
|
if (parts.length === 3) {
|
||||||
@ -662,6 +833,91 @@ const formatTotalDuration = () => {
|
|||||||
return `${hours}小时${minutes}分钟`
|
return `${hours}小时${minutes}分钟`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新评论内容
|
||||||
|
const newComment = ref('')
|
||||||
|
|
||||||
|
// 自动调整textarea高度
|
||||||
|
const adjustTextareaHeight = (event: Event) => {
|
||||||
|
const textarea = event.target as HTMLTextAreaElement
|
||||||
|
textarea.style.height = 'auto'
|
||||||
|
textarea.style.height = textarea.scrollHeight + 'px'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击textarea时调整高度
|
||||||
|
const handleTextareaClick = (event: MouseEvent) => {
|
||||||
|
const textarea = event.target as HTMLTextAreaElement
|
||||||
|
// 如果当前高度是40px,则调整到60px
|
||||||
|
if (textarea.style.height === '40px' || textarea.style.height === '') {
|
||||||
|
textarea.style.height = '60px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交评论函数
|
||||||
|
const submitComment = () => {
|
||||||
|
if (newComment.value.trim()) {
|
||||||
|
const newCommentObj = {
|
||||||
|
id: Date.now(),
|
||||||
|
username: '当前用户',
|
||||||
|
avatar: 'https://via.placeholder.com/40x40/1890ff/ffffff?text=我',
|
||||||
|
time: '刚刚',
|
||||||
|
content: newComment.value,
|
||||||
|
likes: 0
|
||||||
|
}
|
||||||
|
displayComments.value.unshift(newCommentObj)
|
||||||
|
newComment.value = ''
|
||||||
|
// 这里可以调用API提交评论
|
||||||
|
console.log('评论已提交:', newCommentObj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回复相关函数
|
||||||
|
const startReply = (commentId: number, username: string) => {
|
||||||
|
replyingTo.value = commentId
|
||||||
|
replyToUsername.value = username
|
||||||
|
replyText.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelReply = () => {
|
||||||
|
replyingTo.value = null
|
||||||
|
replyToUsername.value = ''
|
||||||
|
replyText.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitReply = () => {
|
||||||
|
if (replyText.value.trim() && replyingTo.value) {
|
||||||
|
const newReplyObj = {
|
||||||
|
id: Date.now(),
|
||||||
|
username: '当前用户',
|
||||||
|
avatar: 'https://via.placeholder.com/40x40/1890ff/ffffff?text=我',
|
||||||
|
time: '刚刚',
|
||||||
|
content: replyText.value,
|
||||||
|
likes: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这里可以调用API提交回复
|
||||||
|
console.log('回复已提交:', newReplyObj)
|
||||||
|
console.log('回复给评论ID:', replyingTo.value)
|
||||||
|
console.log('回复给用户:', replyToUsername.value)
|
||||||
|
|
||||||
|
// 清空回复状态
|
||||||
|
cancelReply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回复文本框高度调整
|
||||||
|
const adjustReplyTextareaHeight = (event: Event) => {
|
||||||
|
const textarea = event.target as HTMLTextAreaElement
|
||||||
|
textarea.style.height = '40px'
|
||||||
|
textarea.style.height = textarea.scrollHeight + 'px'
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReplyTextareaClick = (event: MouseEvent) => {
|
||||||
|
const textarea = event.target as HTMLTextAreaElement
|
||||||
|
if (textarea.style.height === '40px' || !textarea.style.height) {
|
||||||
|
textarea.style.height = '60px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const displayComments = ref([
|
const displayComments = ref([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@ -689,6 +945,11 @@ const displayComments = ref([
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// 回复相关数据
|
||||||
|
const replyingTo = ref<number | null>(null)
|
||||||
|
const replyText = ref('')
|
||||||
|
const replyToUsername = ref('')
|
||||||
|
|
||||||
// 加载课程详情
|
// 加载课程详情
|
||||||
const loadCourseDetail = async () => {
|
const loadCourseDetail = async () => {
|
||||||
console.log('开始加载课程详情,课程ID:', courseId.value)
|
console.log('开始加载课程详情,课程ID:', courseId.value)
|
||||||
@ -2554,6 +2815,191 @@ onMounted(() => {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 发布评论区域 */
|
||||||
|
.post-comment-section {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-input-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-input-area {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-textarea {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid #E6E6E6;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
resize: none;
|
||||||
|
font-family: inherit;
|
||||||
|
height: 40px;
|
||||||
|
min-height: 40px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: height 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #1890ff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-textarea::placeholder {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-left {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
width: 24px;
|
||||||
|
height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1.5px solid #E6E6E6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-icon {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit {
|
||||||
|
background: #0088D1;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 3px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit:hover:not(:disabled) {
|
||||||
|
background: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #bfbfbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 回复输入区域样式 */
|
||||||
|
.reply-input-section {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-input-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-to-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-reply-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-reply-btn:hover {
|
||||||
|
background: #f0f0f0;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-input-container {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #E6E6E6;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-textarea {
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
resize: none;
|
||||||
|
font-family: inherit;
|
||||||
|
height: 40px;
|
||||||
|
min-height: 40px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: height 0.3s ease;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #1890ff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-textarea::placeholder {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
.comment-stats {
|
.comment-stats {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -2617,13 +3063,13 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 8px;
|
/* margin-bottom: 8px; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-username {
|
.comment-username {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
/* font-weight: 600; */
|
||||||
color: #333;
|
color: #666666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-time {
|
.comment-time {
|
||||||
@ -2634,16 +3080,73 @@ onMounted(() => {
|
|||||||
.comment-text {
|
.comment-text {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
color: #666;
|
color: #333;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comment-image-container {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-image-container img {
|
||||||
|
width: 84px;
|
||||||
|
height: 84px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: calc(6 * (84px + 16px));
|
||||||
|
width: 84px;
|
||||||
|
height: 84px;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-images-text {
|
||||||
|
color: white;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.comment-actions {
|
.comment-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.note-icon-container {
|
||||||
|
width: 50px;
|
||||||
|
height: 20px;
|
||||||
|
background: #EDEDED;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-icon-container img {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-icon-container span {
|
||||||
|
color: #666666;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.action-btn {
|
.action-btn {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
@ -2692,6 +3195,131 @@ onMounted(() => {
|
|||||||
background: #d9d9d9;
|
background: #d9d9d9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 回复和二级评论样式 */
|
||||||
|
.comment-replies {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-avatar img {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-username {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-badge {
|
||||||
|
width: 32px;
|
||||||
|
height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-badge.instructor {
|
||||||
|
background: #EEF9FF;
|
||||||
|
color: #008BD7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-badge.user {
|
||||||
|
background: #f6ffed;
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-time {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-text {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #333;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 8px;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-action-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.3s;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-action-btn:hover {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 讲师评论特殊样式 */
|
||||||
|
.reply-item.instructor-reply {}
|
||||||
|
|
||||||
|
.reply-item.instructor-reply .reply-username {
|
||||||
|
color: #666666;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 用户评论样式 */
|
||||||
|
.reply-item.user-reply {}
|
||||||
|
|
||||||
|
.reply-item.user-reply .reply-username {
|
||||||
|
color: #666666;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
/* 右侧边栏 */
|
/* 右侧边栏 */
|
||||||
.sidebar {
|
.sidebar {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -2936,6 +3564,7 @@ onMounted(() => {
|
|||||||
padding-left: 24px;
|
padding-left: 24px;
|
||||||
padding-right: 24px;
|
padding-right: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 响应式设计 */
|
/* 响应式设计 */
|
||||||
@media (max-width: 1399px) and (min-width: 1200px) {
|
@media (max-width: 1399px) and (min-width: 1200px) {
|
||||||
.container {
|
.container {
|
||||||
|
@ -60,13 +60,9 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div v-if="showQualityMenu" class="quality-menu">
|
<div v-if="showQualityMenu" class="quality-menu">
|
||||||
<div
|
<div v-for="quality in videoQualities" :key="quality.value" class="quality-option"
|
||||||
v-for="quality in videoQualities"
|
|
||||||
:key="quality.value"
|
|
||||||
class="quality-option"
|
|
||||||
:class="{ active: quality.value === currentQuality }"
|
:class="{ active: quality.value === currentQuality }"
|
||||||
@click="changeVideoQuality(quality.value); showQualityMenu = false"
|
@click="changeVideoQuality(quality.value); showQualityMenu = false">
|
||||||
>
|
|
||||||
{{ quality.label }}
|
{{ quality.label }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -185,14 +181,36 @@
|
|||||||
<!-- 评论内容 -->
|
<!-- 评论内容 -->
|
||||||
<div v-if="activeTab === 'comments'" class="tab-pane">
|
<div v-if="activeTab === 'comments'" class="tab-pane">
|
||||||
<div class="comments-content">
|
<div class="comments-content">
|
||||||
<!-- <div class="comment-stats">
|
<!-- 发布评论区域 -->
|
||||||
<span class="total-comments">共1251条评论</span>
|
<div class="post-comment-section">
|
||||||
<div class="comment-filters">
|
<div class="comment-input-wrapper">
|
||||||
<button class="filter-btn active">全部</button>
|
<div class="user-avatar">
|
||||||
<button class="filter-btn">最新</button>
|
<img src="/images/activity/6.png" alt="用户头像" />
|
||||||
<button class="filter-btn">最热</button>
|
</div>
|
||||||
|
<div class="comment-input-area">
|
||||||
|
<textarea v-model="newComment" placeholder="写下你的评论..." class="comment-textarea"
|
||||||
|
@input="adjustTextareaHeight" @click="handleTextareaClick"></textarea>
|
||||||
|
<div class="comment-toolbar">
|
||||||
|
<div class="toolbar-left">
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/expression.png" alt="表情" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/@.png" alt="@用户" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/Image.png" alt="图片" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="toolbar-right">
|
||||||
|
<button class="btn-submit" @click="submitComment">
|
||||||
|
发布
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div>
|
||||||
|
|
||||||
<div class="comment-list">
|
<div class="comment-list">
|
||||||
<div class="comment-item" v-for="comment in displayComments" :key="comment.id">
|
<div class="comment-item" v-for="comment in displayComments" :key="comment.id">
|
||||||
@ -202,23 +220,161 @@
|
|||||||
<div class="comment-content">
|
<div class="comment-content">
|
||||||
<div class="comment-header">
|
<div class="comment-header">
|
||||||
<span class="comment-username">{{ comment.username }}</span>
|
<span class="comment-username">{{ comment.username }}</span>
|
||||||
<span class="comment-time">{{ comment.time }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="comment-text">{{ comment.content }}</div>
|
<div class="comment-text">{{ comment.content }}</div>
|
||||||
<div class="comment-actions">
|
<div class="comment-actions">
|
||||||
<button class="action-btn">
|
<button class="action-btn">
|
||||||
<span class="top">置顶评论</span>
|
|
||||||
<span>2025.07.23 16:28</span>
|
<span>2025.07.23 16:28</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="action-btn">回复</button>
|
<button v-if="comment.type === 'note'" class="action-btn">
|
||||||
|
<span class="top">置顶评论</span>
|
||||||
|
</button>
|
||||||
|
<button v-else class="action-btn"
|
||||||
|
@click="startReply(comment.id, comment.username)">回复</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 回复输入区域 -->
|
||||||
|
<div v-if="replyingTo === comment.id" class="reply-input-section">
|
||||||
|
<div class="reply-input-header">
|
||||||
|
<span class="reply-to-text">回复 @{{ replyToUsername }}</span>
|
||||||
|
<button class="cancel-reply-btn" @click="cancelReply">取消</button>
|
||||||
|
</div>
|
||||||
|
<div class="reply-input-container">
|
||||||
|
<textarea v-model="replyText" placeholder="写下你的回复..." class="reply-textarea"
|
||||||
|
@input="adjustReplyTextareaHeight" @click="handleReplyTextareaClick"></textarea>
|
||||||
|
<div class="reply-toolbar">
|
||||||
|
<div class="toolbar-left">
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/expression.png" alt="表情" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/@.png" alt="@用户" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/Image.png" alt="图片" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="toolbar-right">
|
||||||
|
<button class="btn-submit" @click="submitReply" :disabled="!replyText.trim()">
|
||||||
|
发布
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 回复区域示例 -->
|
||||||
|
<div class="comment-replies" v-if="comment.id === 1">
|
||||||
|
<!-- 讲师回复 -->
|
||||||
|
<div class="reply-item instructor-reply">
|
||||||
|
<div class="reply-avatar">
|
||||||
|
<img src="/images/activity/6.png" alt="讲师头像" />
|
||||||
|
</div>
|
||||||
|
<div class="reply-content">
|
||||||
|
<div class="reply-main">
|
||||||
|
<div class="reply-header">
|
||||||
|
<span class="reply-username">张老师</span>
|
||||||
|
<span class="reply-badge instructor">讲师</span>
|
||||||
|
</div>
|
||||||
|
<div class="reply-text">感谢您的反馈!我们会继续优化课程内容,让学习体验更好。</div>
|
||||||
|
</div>
|
||||||
|
<div class="reply-footer">
|
||||||
|
<span class="reply-time">2025.07.23 17:30</span>
|
||||||
|
<div class="reply-actions">
|
||||||
|
<button class="reply-action-btn" @click="startReply(1, '张老师')">回复</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 用户回复 -->
|
||||||
|
<div class="reply-item user-reply">
|
||||||
|
<div class="reply-avatar">
|
||||||
|
<img src="/images/activity/7.png" alt="用户头像" />
|
||||||
|
</div>
|
||||||
|
<div class="reply-content">
|
||||||
|
<div class="reply-main">
|
||||||
|
<div class="reply-header">
|
||||||
|
<span class="reply-username">李同学</span>
|
||||||
|
<span class="reply-badge user">学员</span>
|
||||||
|
</div>
|
||||||
|
<div class="reply-text">同意楼上的观点,这个课程确实很有帮助!</div>
|
||||||
|
</div>
|
||||||
|
<div class="reply-footer">
|
||||||
|
<span class="reply-time">2025.07.23 18:15</span>
|
||||||
|
<div class="reply-actions">
|
||||||
|
<button class="reply-action-btn" @click="startReply(1, '李同学')">回复</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="comment-item">
|
||||||
|
<div class="comment-avatar">
|
||||||
|
<img
|
||||||
|
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80"
|
||||||
|
alt="张老师" />
|
||||||
|
</div>
|
||||||
|
<div class="comment-content">
|
||||||
|
<div class="comment-header">
|
||||||
|
<span class="comment-username">张老师</span>
|
||||||
|
</div>
|
||||||
|
<div class="comment-text">这个课程内容很实用,讲解得很清楚,对初学者很有帮助!111</div>
|
||||||
|
<div class="comment-image-container">
|
||||||
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
||||||
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
||||||
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
||||||
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
||||||
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
||||||
|
<img src="/images/courses/course1.png" alt="课程图片" class="comment-image">
|
||||||
|
<div class="image-overlay">
|
||||||
|
<span class="more-images-text">+6</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="comment-actions">
|
||||||
|
<div class="note-icon-container">
|
||||||
|
<img src="/images/courses/comments-note.png" alt="笔记" class="note-icon">
|
||||||
|
<span>笔记</span>
|
||||||
|
</div>
|
||||||
|
<span class="comment-time">2025.07.23 16:28</span>
|
||||||
|
<button class="action-btn" @click="startReply(1, '张老师')">回复</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 回复输入区域 -->
|
||||||
|
<div v-if="replyingTo === 1" class="reply-input-section">
|
||||||
|
<div class="reply-input-header">
|
||||||
|
<span class="reply-to-text">回复 @{{ replyToUsername }}</span>
|
||||||
|
<button class="cancel-reply-btn" @click="cancelReply">取消</button>
|
||||||
|
</div>
|
||||||
|
<div class="reply-input-container">
|
||||||
|
<textarea v-model="replyText" placeholder="写下你的回复..." class="reply-textarea"
|
||||||
|
@input="adjustReplyTextareaHeight" @click="handleReplyTextareaClick"></textarea>
|
||||||
|
<div class="reply-toolbar">
|
||||||
|
<div class="toolbar-left">
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/expression.png" alt="表情" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/@.png" alt="@用户" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
<button class="toolbar-btn">
|
||||||
|
<img src="/images/courses/Image.png" alt="图片" class="toolbar-icon" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="toolbar-right">
|
||||||
|
<button class="btn-submit" @click="submitReply" :disabled="!replyText.trim()">
|
||||||
|
发布
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div class="load-more">
|
|
||||||
<button class="btn-load-more">加载更多评论</button>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -544,7 +700,8 @@ const displayComments = ref([
|
|||||||
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
|
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
|
||||||
time: '2天前',
|
time: '2天前',
|
||||||
content: '老师讲得很详细,从零基础到实际应用都有涉及,非常适合初学者!',
|
content: '老师讲得很详细,从零基础到实际应用都有涉及,非常适合初学者!',
|
||||||
likes: 23
|
likes: 23,
|
||||||
|
type: 'comment'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
@ -552,7 +709,8 @@ const displayComments = ref([
|
|||||||
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
|
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
|
||||||
time: '5天前',
|
time: '5天前',
|
||||||
content: '课程内容很实用,跟着做了几个项目,收获很大。推荐给想学AI的朋友们!',
|
content: '课程内容很实用,跟着做了几个项目,收获很大。推荐给想学AI的朋友们!',
|
||||||
likes: 18
|
likes: 18,
|
||||||
|
type: 'comment'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
@ -560,10 +718,102 @@ const displayComments = ref([
|
|||||||
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
|
avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&auto=format&fit=crop&w=50&q=80',
|
||||||
time: '1周前',
|
time: '1周前',
|
||||||
content: 'DeepSeek确实是个很强大的工具,通过这个课程学会了很多实用技巧。',
|
content: 'DeepSeek确实是个很强大的工具,通过这个课程学会了很多实用技巧。',
|
||||||
likes: 31
|
likes: 31,
|
||||||
|
type: 'comment'
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// 新评论内容
|
||||||
|
const newComment = ref('')
|
||||||
|
|
||||||
|
// 自动调整textarea高度
|
||||||
|
const adjustTextareaHeight = (event: Event) => {
|
||||||
|
const textarea = event.target as HTMLTextAreaElement
|
||||||
|
textarea.style.height = 'auto'
|
||||||
|
textarea.style.height = textarea.scrollHeight + 'px'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击textarea时调整高度
|
||||||
|
const handleTextareaClick = (event: MouseEvent) => {
|
||||||
|
const textarea = event.target as HTMLTextAreaElement
|
||||||
|
// 如果当前高度是40px,则调整到60px
|
||||||
|
if (textarea.style.height === '40px' || textarea.style.height === '') {
|
||||||
|
textarea.style.height = '60px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交评论函数
|
||||||
|
const submitComment = () => {
|
||||||
|
if (newComment.value.trim()) {
|
||||||
|
const newCommentObj = {
|
||||||
|
id: Date.now(),
|
||||||
|
username: '当前用户',
|
||||||
|
avatar: 'https://via.placeholder.com/40x40/1890ff/ffffff?text=我',
|
||||||
|
time: '刚刚',
|
||||||
|
content: newComment.value,
|
||||||
|
likes: 0,
|
||||||
|
type: 'comment'
|
||||||
|
}
|
||||||
|
displayComments.value.unshift(newCommentObj)
|
||||||
|
newComment.value = ''
|
||||||
|
// 这里可以调用API提交评论
|
||||||
|
console.log('评论已提交:', newCommentObj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回复相关函数
|
||||||
|
const startReply = (commentId: number, username: string) => {
|
||||||
|
replyingTo.value = commentId
|
||||||
|
replyToUsername.value = username
|
||||||
|
replyText.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelReply = () => {
|
||||||
|
replyingTo.value = null
|
||||||
|
replyToUsername.value = ''
|
||||||
|
replyText.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitReply = () => {
|
||||||
|
if (replyText.value.trim() && replyingTo.value) {
|
||||||
|
const newReplyObj = {
|
||||||
|
id: Date.now(),
|
||||||
|
username: '当前用户',
|
||||||
|
avatar: 'https://via.placeholder.com/40x40/1890ff/ffffff?text=我',
|
||||||
|
time: '刚刚',
|
||||||
|
content: replyText.value,
|
||||||
|
likes: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这里可以调用API提交回复
|
||||||
|
console.log('回复已提交:', newReplyObj)
|
||||||
|
console.log('回复给评论ID:', replyingTo.value)
|
||||||
|
console.log('回复给用户:', replyToUsername.value)
|
||||||
|
|
||||||
|
// 清空回复状态
|
||||||
|
cancelReply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回复文本框高度调整
|
||||||
|
const adjustReplyTextareaHeight = (event: Event) => {
|
||||||
|
const textarea = event.target as HTMLTextAreaElement
|
||||||
|
textarea.style.height = '40px'
|
||||||
|
textarea.style.height = textarea.scrollHeight + 'px'
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReplyTextareaClick = (event: MouseEvent) => {
|
||||||
|
const textarea = event.target as HTMLTextAreaElement
|
||||||
|
if (textarea.style.height === '40px' || !textarea.style.height) {
|
||||||
|
textarea.style.height = '60px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回复相关数据
|
||||||
|
const replyingTo = ref<number | null>(null)
|
||||||
|
const replyText = ref('')
|
||||||
|
const replyToUsername = ref('')
|
||||||
|
|
||||||
// 生成模拟章节数据(暂时禁用)
|
// 生成模拟章节数据(暂时禁用)
|
||||||
const generateMockSections = (): CourseSection[] => {
|
const generateMockSections = (): CourseSection[] => {
|
||||||
// 暂时返回空数组,等待API修复
|
// 暂时返回空数组,等待API修复
|
||||||
@ -1232,7 +1482,6 @@ onUnmounted(() => {
|
|||||||
.video-player {
|
.video-player {
|
||||||
background: #000;
|
background: #000;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-container {
|
.video-container {
|
||||||
@ -1566,7 +1815,6 @@ onUnmounted(() => {
|
|||||||
.course-tabs {
|
.course-tabs {
|
||||||
padding: 14px 24px;
|
padding: 14px 24px;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2905,4 +3153,440 @@ onUnmounted(() => {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 评论区样式 */
|
||||||
|
.comments-content {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 发布评论区域 */
|
||||||
|
.post-comment-section {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-input-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-input-area {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-textarea {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid #E6E6E6;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
resize: none;
|
||||||
|
font-family: inherit;
|
||||||
|
height: 40px;
|
||||||
|
min-height: 40px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: height 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #1890ff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-textarea::placeholder {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-left {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
width: 24px;
|
||||||
|
height: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1.5px solid #E6E6E6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-icon {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit {
|
||||||
|
background: #0088D1;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 3px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit:hover:not(:disabled) {
|
||||||
|
background: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #bfbfbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 回复输入区域样式 */
|
||||||
|
.reply-input-section {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-input-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-to-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-reply-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-reply-btn:hover {
|
||||||
|
background: #f0f0f0;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-input-container {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #E6E6E6;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-textarea {
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
resize: none;
|
||||||
|
font-family: inherit;
|
||||||
|
height: 40px;
|
||||||
|
min-height: 40px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: height 0.3s ease;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #1890ff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-textarea::placeholder {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-avatar img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-username {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-time {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-text {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-image-container {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-image-container img {
|
||||||
|
width: 84px;
|
||||||
|
height: 84px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: calc(6 * (84px + 16px));
|
||||||
|
width: 84px;
|
||||||
|
height: 84px;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-images-text {
|
||||||
|
color: white;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-icon-container {
|
||||||
|
width: 50px;
|
||||||
|
height: 20px;
|
||||||
|
background: #EDEDED;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-icon-container img {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-icon-container span {
|
||||||
|
color: #666666;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn:hover {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn span {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn .top {
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #FF304B;
|
||||||
|
background-color: #FFF4F4;
|
||||||
|
border-radius: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 回复和二级评论样式 */
|
||||||
|
.comment-replies {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-avatar img {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-username {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-badge {
|
||||||
|
width: 32px;
|
||||||
|
height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-badge.instructor {
|
||||||
|
background: #EEF9FF;
|
||||||
|
color: #008BD7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-badge.user {
|
||||||
|
background: #f6ffed;
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-time {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-text {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #333;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 8px;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-action-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-action-btn:hover {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
<div class="answer-sheet-header">
|
<div class="answer-sheet-header">
|
||||||
<h4>练习题目</h4>
|
<h4>练习题目</h4>
|
||||||
<span class="progress-text">{{ String(currentQuestionIndex + 1).padStart(2, '0') }}<span>/{{
|
<span class="progress-text">{{ String(currentQuestionIndex + 1).padStart(2, '0') }}<span>/{{
|
||||||
String(questions.length).padStart(2, '0') }}</span></span>
|
String(questions.length).padStart(2, '0') }}</span></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="question-grid">
|
<div class="question-grid">
|
||||||
<!-- 单选题 -->
|
<!-- 单选题 -->
|
||||||
@ -430,40 +430,84 @@
|
|||||||
<div class="modal-overlay" @click="closePracticeResult"></div>
|
<div class="modal-overlay" @click="closePracticeResult"></div>
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h3>练习完成</h3>
|
<h3>答题报告</h3>
|
||||||
<button class="close-btn" @click="closePracticeResult">×</button>
|
<!-- <button class="close-btn" @click="closePracticeResult">×</button> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="practice-summary">
|
<!-- 得分展示区域 -->
|
||||||
<div class="summary-header">
|
<div class="score-display">
|
||||||
<h4>练习总结</h4>
|
<div class="difficulty-tag">难度:4.8</div>
|
||||||
</div>
|
<div class="score-gauge">
|
||||||
<div class="score-section">
|
<div class="gauge-circle">
|
||||||
<div class="score-card">
|
<div class="gauge-fill" :style="{ transform: `rotate(${Math.min((finalScore / 120) * 180, 180)}deg)` }">
|
||||||
<div class="score-number">{{ finalScore }}</div>
|
</div>
|
||||||
<div class="score-label">总分</div>
|
<div class="score-content">
|
||||||
</div>
|
<div class="score-label">我的得分</div>
|
||||||
<div class="score-card">
|
<div class="score-number">{{ finalScore }}</div>
|
||||||
<div class="score-number">{{ correctCount }}</div>
|
</div>
|
||||||
<div class="score-label">正确题数</div>
|
|
||||||
</div>
|
|
||||||
<div class="score-card">
|
|
||||||
<div class="score-number">{{ questions.length - correctCount }}</div>
|
|
||||||
<div class="score-label">错误题数</div>
|
|
||||||
</div>
|
|
||||||
<div class="score-card">
|
|
||||||
<div class="score-number">{{ questions.length > 0 ? Math.round((correctCount / questions.length) * 100)
|
|
||||||
: 0 }}%</div>
|
|
||||||
<div class="score-label">正确率</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
<!-- 答题详情 -->
|
||||||
<div class="practice-actions">
|
<div class="quiz-details">
|
||||||
<button class="btn-review-practice" @click="reviewPractice">查看练习详情</button>
|
<div class="detail-item">
|
||||||
<button class="btn-return-course" @click="goBack">返回课程</button>
|
<span class="detail-label">答题时间:</span>
|
||||||
<button class="btn-return-home" @click="goHome">返回首页</button>
|
<span class="detail-value">{{ new Date().toLocaleDateString('zh-CN') }} {{ new
|
||||||
|
Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">答题时长:</span>
|
||||||
|
<span class="detail-value">56分23秒</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">答题总数:</span>
|
||||||
|
<span class="detail-value">{{ correctCount }}/{{ questions.length }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 得分情况 -->
|
||||||
|
<div class="score-breakdown">
|
||||||
|
<h4>得分情况</h4>
|
||||||
|
<div class="breakdown-items">
|
||||||
|
<div class="breakdown-item">
|
||||||
|
<span class="item-label">单选</span>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" style="width: 80%"></div>
|
||||||
|
</div>
|
||||||
|
<span class="item-score"><span class="item-score-number">8</span>/10</span>
|
||||||
|
</div>
|
||||||
|
<div class="breakdown-item">
|
||||||
|
<span class="item-label">多选</span>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" style="width: 60%"></div>
|
||||||
|
</div>
|
||||||
|
<span class="item-score"><span class="item-score-number">6</span>/10</span>
|
||||||
|
</div>
|
||||||
|
<div class="breakdown-item">
|
||||||
|
<span class="item-label">判断</span>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" style="width: 90%"></div>
|
||||||
|
</div>
|
||||||
|
<span class="item-score"><span class="item-score-number">9</span>/10</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 统计数据 -->
|
||||||
|
<div class="statistics">
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-number orange">120</div>
|
||||||
|
<div class="stat-label">最高分</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-number red">第 <span class="stat-number-rank">10</span> 名</div>
|
||||||
|
<div class="stat-label">练习人数3892人</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-number blue">90</div>
|
||||||
|
<div class="stat-label">答对最多</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1381,7 +1425,7 @@ const confirmSubmit = () => {
|
|||||||
calculatePracticeResult()
|
calculatePracticeResult()
|
||||||
showPracticeResultModal.value = true
|
showPracticeResultModal.value = true
|
||||||
} else {
|
} else {
|
||||||
executeSubmit()
|
executeSubmit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1496,7 +1540,7 @@ onMounted(() => {
|
|||||||
initializeAnswers()
|
initializeAnswers()
|
||||||
|
|
||||||
// 练习页面直接开始,不需要等待用户点击
|
// 练习页面直接开始,不需要等待用户点击
|
||||||
startExam()
|
startExam()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 关闭练习结果弹窗
|
// 关闭练习结果弹窗
|
||||||
@ -2543,8 +2587,8 @@ const reviewPractice = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.score-label {
|
.score-label {
|
||||||
font-size: 16px;
|
font-size: 12px;
|
||||||
color: #666;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.score-value {
|
.score-value {
|
||||||
@ -2977,109 +3021,15 @@ const reviewPractice = () => {
|
|||||||
.modal-content {
|
.modal-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
padding: 30px;
|
|
||||||
text-align: center;
|
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 516px;
|
height: 520px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header {
|
|
||||||
margin-bottom: 25px;
|
|
||||||
padding-bottom: 15px;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-header h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn {
|
|
||||||
position: absolute;
|
|
||||||
top: 15px;
|
|
||||||
right: 15px;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 24px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #999;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn:hover {
|
|
||||||
color: #666;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-body {
|
|
||||||
margin-bottom: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.practice-summary {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-header {
|
|
||||||
font-size: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-header h4 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-section {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 25px;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-card {
|
|
||||||
flex: 1;
|
|
||||||
padding: 0 10px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-number {
|
|
||||||
font-size: 42px;
|
|
||||||
line-height: 1;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-number:first-child {
|
|
||||||
color: #52c41a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-number:nth-child(2) {
|
|
||||||
color: #52c41a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-number:nth-child(3) {
|
|
||||||
color: #ff4d4f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-number:last-child {
|
|
||||||
color: #1890ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-label {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-footer {
|
.modal-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -3173,4 +3123,245 @@ const reviewPractice = () => {
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* 答题报告样式 */
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
border-bottom: 1.5px solid #E6E6E6;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #000;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.difficulty-tag {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 0;
|
||||||
|
background: #0288D1;
|
||||||
|
color: white;
|
||||||
|
width: 56px;
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
border-radius: 4px 4px 4px 0;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #999;
|
||||||
|
padding: 0;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
/* padding: 20px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 得分展示区域 */
|
||||||
|
.score-display {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
/* margin-bottom: 10px; */
|
||||||
|
padding: 25px 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-gauge {
|
||||||
|
position: relative;
|
||||||
|
width: 180px;
|
||||||
|
height: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gauge-circle {
|
||||||
|
width: 180px;
|
||||||
|
height: 90px;
|
||||||
|
position: relative;
|
||||||
|
background-image: url('/images/examination/score-bg.png');
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gauge-fill {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 80%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
text-align: center;
|
||||||
|
z-index: 2;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-number {
|
||||||
|
font-size: 40px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 答题详情 */
|
||||||
|
.quiz-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 0 34px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
gap: 2px;
|
||||||
|
border-bottom: 1.5px solid #E6E6E6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 得分情况 */
|
||||||
|
.score-breakdown {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
border-bottom: 1.5px solid #E6E6E6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-breakdown h4 {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breakdown-items {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breakdown-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666666;
|
||||||
|
font-weight: 500;
|
||||||
|
min-width: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
flex: 1;
|
||||||
|
height: 8px;
|
||||||
|
background: #E2F5FF;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: #0288D1;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: width 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-score {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666666;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-score-number {
|
||||||
|
color: #0288D1;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 统计数据 */
|
||||||
|
.statistics {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number.orange {
|
||||||
|
color: #F38505;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number.red {
|
||||||
|
color: #999;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number.red span {
|
||||||
|
color: #FF3636;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number.blue {
|
||||||
|
color: #3F76ED;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #999;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="profile-page">
|
<div class="profile-page">
|
||||||
|
<!-- 刷新遮罩 -->
|
||||||
|
<div v-if="isRefreshing" class="refresh-mask">
|
||||||
|
<div class="refresh-content">
|
||||||
|
<div class="refresh-spinner"></div>
|
||||||
|
<p>正在刷新...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- 主要内容区域 -->
|
<!-- 主要内容区域 -->
|
||||||
<div class="profile-content flex-row">
|
<div class="profile-content flex-row">
|
||||||
<!-- 左侧侧边栏 -->
|
<!-- 左侧侧边栏 -->
|
||||||
@ -231,7 +238,7 @@
|
|||||||
fontSize: '14px'
|
fontSize: '14px'
|
||||||
}">
|
}">
|
||||||
{{ detailAssignment.status === '未完成' || detailAssignment.status === '待提交' ? '未完成' :
|
{{ detailAssignment.status === '未完成' || detailAssignment.status === '待提交' ? '未完成' :
|
||||||
(detailAssignment.status === '已完成' ? '已完成' : '541人已完成') }}
|
(detailAssignment.status === '已完成' ? '已完成' : '541人已完成') }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -660,7 +667,7 @@
|
|||||||
<div class="activity-status-left">
|
<div class="activity-status-left">
|
||||||
<span :class="['activity-status-text', activity.status]">{{ activity.status === 'ongoing' ? '进行中' :
|
<span :class="['activity-status-text', activity.status]">{{ activity.status === 'ongoing' ? '进行中' :
|
||||||
'已结束'
|
'已结束'
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1024,38 +1031,42 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 上传作业弹窗 -->
|
<!-- 上传作业弹窗 -->
|
||||||
<n-modal v-model:show="showModal" preset="card" title="上传作业" style="width: 600px;" :mask-closable="false">
|
<div v-if="showModal" class="custom-modal-overlay" @click="closeUploadModal">
|
||||||
<template v-if="currentAssignment">
|
<div class="custom-modal" @click.stop>
|
||||||
<div class="assignment-info">
|
<div v-if="currentAssignment" class="modal-content">
|
||||||
<h3>{{ currentAssignment.title }}</h3>
|
<n-form>
|
||||||
<p>{{ currentAssignment.description }}</p>
|
<div class="form-item">
|
||||||
</div>
|
<label class="form-label">标题名称 <span class="required">*</span></label>
|
||||||
<n-form>
|
<input v-model="uploadForm.title" type="text" class="form-input" placeholder="" />
|
||||||
<n-form-item label="标题名称" required>
|
|
||||||
<n-input v-model:value="uploadForm.title" placeholder="请输入标题" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="编辑内容" required>
|
|
||||||
<n-input v-model:value="uploadForm.content" type="textarea" placeholder="请输入内容"
|
|
||||||
:autosize="{ minRows: 3, maxRows: 6 }" />
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item label="上传文件" required>
|
|
||||||
<n-upload multiple directory-dnd :custom-request="customRequest" :default-upload="false">
|
|
||||||
<n-button>选择文件</n-button>
|
|
||||||
</n-upload>
|
|
||||||
<div class="file-list" v-if="uploadForm.files.length > 0">
|
|
||||||
<div v-for="(file, index) in uploadForm.files" :key="index" class="file-item">
|
|
||||||
<span>{{ file.name }}</span>
|
|
||||||
<span>{{ (file.size / 1024).toFixed(2) }} KB</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</n-form-item>
|
<n-form-item label="编辑内容" required>
|
||||||
</n-form>
|
<QuillEditor v-model="uploadForm.content" placeholder="请输入内容" height="400px" />
|
||||||
<div class="modal-footer">
|
</n-form-item>
|
||||||
<n-button @click="closeUploadModal">取消</n-button>
|
<!-- <n-form-item label="上传文件" required>
|
||||||
<n-button type="primary" @click="submitAssignment" style="margin-left: 12px;">确认</n-button>
|
<n-upload multiple directory-dnd :custom-request="customRequest" :default-upload="false">
|
||||||
|
<n-button>选择文件</n-button>
|
||||||
|
</n-upload>
|
||||||
|
<div class="file-list" v-if="uploadForm.files.length > 0">
|
||||||
|
<div v-for="(file, index) in uploadForm.files" :key="index" class="file-item">
|
||||||
|
<div class="file-icon">
|
||||||
|
<img src="/images/auth/file.png" alt="文件图标" />
|
||||||
|
</div>
|
||||||
|
<div class="file-info">
|
||||||
|
<div class="file-name">{{ file.name }}</div>
|
||||||
|
<div class="file-size">{{ (file.size / 1024).toFixed(2) }}KB</div>
|
||||||
|
</div>
|
||||||
|
<button class="file-delete" @click="removeFile(index)">🗑️</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-form-item> -->
|
||||||
|
</n-form>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="primary" @click="submitAssignment" style="margin-left: 12px;">确认</button>
|
||||||
|
<button @click="closeUploadModal" class="cancel">取消</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</n-modal>
|
</div>
|
||||||
|
|
||||||
<!-- 考试详情页面 -->
|
<!-- 考试详情页面 -->
|
||||||
<ExamDetail v-if="showExamDetail && currentExamDetail" :currentExamDetail="currentExamDetail"
|
<ExamDetail v-if="showExamDetail && currentExamDetail" :currentExamDetail="currentExamDetail"
|
||||||
@ -1065,12 +1076,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, reactive } from 'vue'
|
import { ref, computed, onMounted, onActivated, reactive } from 'vue'
|
||||||
import { useMessage, NModal, NUpload, NInput, NForm, NFormItem, NButton } from 'naive-ui'
|
import { useMessage, NUpload, NInput, NForm, NFormItem, NButton } from 'naive-ui'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import SafeAvatar from '@/components/common/SafeAvatar.vue'
|
import SafeAvatar from '@/components/common/SafeAvatar.vue'
|
||||||
import ExamDetail from '../components/ExamDetail.vue';
|
import ExamDetail from '../components/ExamDetail.vue';
|
||||||
|
import QuillEditor from '@/components/common/QuillEditor.vue'
|
||||||
|
|
||||||
const { t, locale } = useI18n()
|
const { t, locale } = useI18n()
|
||||||
|
|
||||||
@ -1278,6 +1290,7 @@ const activeDownloadTab = ref('courseware')
|
|||||||
const activeFileMenu = ref<number | null>(null)
|
const activeFileMenu = ref<number | null>(null)
|
||||||
const currentPath = ref<string[]>([]) // 当前路径,用于面包屑
|
const currentPath = ref<string[]>([]) // 当前路径,用于面包屑
|
||||||
const isInSubDirectory = ref(false) // 是否在子目录中
|
const isInSubDirectory = ref(false) // 是否在子目录中
|
||||||
|
const isRefreshing = ref(false) // 是否正在刷新
|
||||||
|
|
||||||
// 下载筛选条件
|
// 下载筛选条件
|
||||||
const downloadFilter = reactive({
|
const downloadFilter = reactive({
|
||||||
@ -2764,12 +2777,82 @@ const customRequest = ({ file, onFinish }: any) => {
|
|||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除文件
|
||||||
|
const removeFile = (index: number) => {
|
||||||
|
uploadForm.files.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 初始化
|
// 初始化
|
||||||
|
// 检查是否需要刷新
|
||||||
|
const shouldRefresh = sessionStorage.getItem('refreshProfile')
|
||||||
|
if (shouldRefresh === 'true') {
|
||||||
|
isRefreshing.value = true
|
||||||
|
sessionStorage.removeItem('refreshProfile')
|
||||||
|
|
||||||
|
// 延迟刷新,给用户一个视觉反馈
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload()
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
// 检查是否需要刷新
|
||||||
|
const shouldRefresh = sessionStorage.getItem('refreshProfile')
|
||||||
|
if (shouldRefresh === 'true') {
|
||||||
|
isRefreshing.value = true
|
||||||
|
sessionStorage.removeItem('refreshProfile')
|
||||||
|
|
||||||
|
// 延迟刷新,给用户一个视觉反馈
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload()
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* 刷新遮罩样式 */
|
||||||
|
.refresh-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-content {
|
||||||
|
text-align: center;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-top: 4px solid #3498db;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* 基础布局类 */
|
/* 基础布局类 */
|
||||||
.flex-col {
|
.flex-col {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -4664,9 +4747,26 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-footer {
|
.modal-footer {
|
||||||
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-start;
|
||||||
margin-top: 20px;
|
margin-top: -30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer button {
|
||||||
|
width: 100px;
|
||||||
|
height: 42px;
|
||||||
|
background: #0088D1;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel {
|
||||||
|
background: #E2F5FF !important;
|
||||||
|
color: #0088D1 !important;
|
||||||
|
border: 1px solid #0088D1 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 作业详情视图样式 */
|
/* 作业详情视图样式 */
|
||||||
@ -8080,4 +8180,154 @@ onMounted(() => {
|
|||||||
padding: 0 1vw !important;
|
padding: 0 1vw !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 文件列表样式 */
|
||||||
|
.file-list {
|
||||||
|
margin-top: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
min-width: 200px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-icon img {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-size {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-delete {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-delete:hover {
|
||||||
|
color: #d03050;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自定义弹窗样式 */
|
||||||
|
.custom-modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-modal {
|
||||||
|
background: #F7F7F7;
|
||||||
|
width: 799px;
|
||||||
|
max-width: 90vw;
|
||||||
|
max-height: 758px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
padding: 22px;
|
||||||
|
width: 100%;
|
||||||
|
background: #F7F7F7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 弹窗表单标签样式 */
|
||||||
|
.n-form-item-label {
|
||||||
|
font-size: 18px !important;
|
||||||
|
color: #333 !important;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 弹窗表单项目间距 */
|
||||||
|
.n-form-item {
|
||||||
|
margin-bottom: 24px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 弹窗表单项目内部间距 */
|
||||||
|
.n-form-item-blank {
|
||||||
|
margin-top: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 弹窗底部按钮区域 */
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自定义表单样式 */
|
||||||
|
.form-item {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required {
|
||||||
|
color: #d03050;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 50px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
background: white;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #18a058;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input::placeholder {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
</style>
|
</style>
|