commit
This commit is contained in:
@@ -4,11 +4,11 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Vite App</title>
|
<title>NBA在线</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
<script type="text/javascript" src="//js.users.51.la/21957239.js"></script>
|
<script type="text/javascript" src="https://js.users.51.la/21957239.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -15,7 +15,9 @@
|
|||||||
"element-plus": "^2.9.7",
|
"element-plus": "^2.9.7",
|
||||||
"flv.js": "^1.6.2",
|
"flv.js": "^1.6.2",
|
||||||
"hls.js": "^1.6.2",
|
"hls.js": "^1.6.2",
|
||||||
|
"pinia": "^3.0.2",
|
||||||
"router": "^2.2.0",
|
"router": "^2.2.0",
|
||||||
|
"video.js": "^8.22.0",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "4"
|
"vue-router": "4"
|
||||||
},
|
},
|
||||||
|
|||||||
201
pnpm-lock.yaml
generated
201
pnpm-lock.yaml
generated
@@ -26,9 +26,15 @@ importers:
|
|||||||
hls.js:
|
hls.js:
|
||||||
specifier: ^1.6.2
|
specifier: ^1.6.2
|
||||||
version: 1.6.2
|
version: 1.6.2
|
||||||
|
pinia:
|
||||||
|
specifier: ^3.0.2
|
||||||
|
version: 3.0.2(vue@3.5.13)
|
||||||
router:
|
router:
|
||||||
specifier: ^2.2.0
|
specifier: ^2.2.0
|
||||||
version: 2.2.0
|
version: 2.2.0
|
||||||
|
video.js:
|
||||||
|
specifier: ^8.22.0
|
||||||
|
version: 8.22.0
|
||||||
vue:
|
vue:
|
||||||
specifier: ^3.5.13
|
specifier: ^3.5.13
|
||||||
version: 3.5.13
|
version: 3.5.13
|
||||||
@@ -182,6 +188,10 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@babel/core': ^7.0.0-0
|
'@babel/core': ^7.0.0-0
|
||||||
|
|
||||||
|
'@babel/runtime@7.27.0':
|
||||||
|
resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/template@7.27.0':
|
'@babel/template@7.27.0':
|
||||||
resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==}
|
resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -528,6 +538,19 @@ packages:
|
|||||||
'@types/web-bluetooth@0.0.16':
|
'@types/web-bluetooth@0.0.16':
|
||||||
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
|
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
|
||||||
|
|
||||||
|
'@videojs/http-streaming@3.17.0':
|
||||||
|
resolution: {integrity: sha512-Ch1P3tvvIEezeZXyK11UfWgp4cWKX4vIhZ30baN/lRinqdbakZ5hiAI3pGjRy3d+q/Epyc8Csz5xMdKNNGYpcw==}
|
||||||
|
engines: {node: '>=8', npm: '>=5'}
|
||||||
|
peerDependencies:
|
||||||
|
video.js: ^8.19.0
|
||||||
|
|
||||||
|
'@videojs/vhs-utils@4.1.1':
|
||||||
|
resolution: {integrity: sha512-5iLX6sR2ownbv4Mtejw6Ax+naosGvoT9kY+gcuHzANyUZZ+4NpeNdKMUhb6ag0acYej1Y7cmr/F2+4PrggMiVA==}
|
||||||
|
engines: {node: '>=8', npm: '>=5'}
|
||||||
|
|
||||||
|
'@videojs/xhr@2.7.0':
|
||||||
|
resolution: {integrity: sha512-giab+EVRanChIupZK7gXjHy90y3nncA2phIOyG3Ne5fvpiMJzvqYwiTOnEVW2S4CoYcuKJkomat7bMXA/UoUZQ==}
|
||||||
|
|
||||||
'@vitejs/plugin-vue@5.2.3':
|
'@vitejs/plugin-vue@5.2.3':
|
||||||
resolution: {integrity: sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==}
|
resolution: {integrity: sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==}
|
||||||
engines: {node: ^18.0.0 || >=20.0.0}
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
@@ -566,6 +589,9 @@ packages:
|
|||||||
'@vue/devtools-api@6.6.4':
|
'@vue/devtools-api@6.6.4':
|
||||||
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
|
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
|
||||||
|
|
||||||
|
'@vue/devtools-api@7.7.5':
|
||||||
|
resolution: {integrity: sha512-HYV3tJGARROq5nlVMJh5KKHk7GU8Au3IrrmNNqr978m0edxgpHgYPDoNUGrvEgIbObz09SQezFR3A1EVmB5WZg==}
|
||||||
|
|
||||||
'@vue/devtools-core@7.7.5':
|
'@vue/devtools-core@7.7.5':
|
||||||
resolution: {integrity: sha512-ElKr0NDor57gVaT+gMQ8kcVP4uFGqHcxuuQndW/rPwh6aHWvEcUL3sxL8cEk+e1Rdt28kS88erpsiIMO6hEENQ==}
|
resolution: {integrity: sha512-ElKr0NDor57gVaT+gMQ8kcVP4uFGqHcxuuQndW/rPwh6aHWvEcUL3sxL8cEk+e1Rdt28kS88erpsiIMO6hEENQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -603,6 +629,13 @@ packages:
|
|||||||
'@vueuse/shared@9.13.0':
|
'@vueuse/shared@9.13.0':
|
||||||
resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
|
resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
|
||||||
|
|
||||||
|
'@xmldom/xmldom@0.8.10':
|
||||||
|
resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
|
aes-decrypter@4.0.2:
|
||||||
|
resolution: {integrity: sha512-lc+/9s6iJvuaRe5qDlMTpCFjnwpkeOXp8qP3oiZ5jsj1MRg+SBVUmmICrhxHvc8OELSmc+fEyyxAuppY6hrWzw==}
|
||||||
|
|
||||||
async-validator@4.2.5:
|
async-validator@4.2.5:
|
||||||
resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
|
resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
|
||||||
|
|
||||||
@@ -693,6 +726,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
dom-walk@0.1.2:
|
||||||
|
resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==}
|
||||||
|
|
||||||
dplayer@1.27.1:
|
dplayer@1.27.1:
|
||||||
resolution: {integrity: sha512-2laBMXs5V1B9zPwJ7eAIw/OBo+Xjvy03i4GHTk3Cg+IWbrq8rKMFO0fFr6ClAYotYOCcFGOvaJDkOZcgKllsCA==}
|
resolution: {integrity: sha512-2laBMXs5V1B9zPwJ7eAIw/OBo+Xjvy03i4GHTk3Cg+IWbrq8rKMFO0fFr6ClAYotYOCcFGOvaJDkOZcgKllsCA==}
|
||||||
|
|
||||||
@@ -809,6 +845,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
|
resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
global@4.4.0:
|
||||||
|
resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==}
|
||||||
|
|
||||||
globals@11.12.0:
|
globals@11.12.0:
|
||||||
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -854,6 +893,9 @@ packages:
|
|||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
is-function@1.0.2:
|
||||||
|
resolution: {integrity: sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==}
|
||||||
|
|
||||||
is-inside-container@1.0.0:
|
is-inside-container@1.0.0:
|
||||||
resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
|
resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
|
||||||
engines: {node: '>=14.16'}
|
engines: {node: '>=14.16'}
|
||||||
@@ -920,6 +962,9 @@ packages:
|
|||||||
lru-cache@5.1.1:
|
lru-cache@5.1.1:
|
||||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||||
|
|
||||||
|
m3u8-parser@7.2.0:
|
||||||
|
resolution: {integrity: sha512-CRatFqpjVtMiMaKXxNvuI3I++vUumIXVVT/JpCpdU/FynV/ceVw1qpPyyBNindL+JlPMSesx+WX1QJaZEJSaMQ==}
|
||||||
|
|
||||||
magic-string@0.30.17:
|
magic-string@0.30.17:
|
||||||
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
||||||
|
|
||||||
@@ -938,9 +983,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
min-document@2.19.0:
|
||||||
|
resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==}
|
||||||
|
|
||||||
mitt@3.0.1:
|
mitt@3.0.1:
|
||||||
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||||
|
|
||||||
|
mpd-parser@1.3.1:
|
||||||
|
resolution: {integrity: sha512-1FuyEWI5k2HcmhS1HkKnUAQV7yFPfXPht2DnRRGtoiiAAW+ESTbtEXIDpRkwdU+XyrQuwrIym7UkoPKsZ0SyFw==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
mrmime@2.0.1:
|
mrmime@2.0.1:
|
||||||
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -948,6 +1000,11 @@ packages:
|
|||||||
ms@2.1.3:
|
ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
|
mux.js@7.1.0:
|
||||||
|
resolution: {integrity: sha512-NTxawK/BBELJrYsZThEulyUMDVlLizKdxyAsMuzoCD1eFj97BVaA8D/CvKsKu6FOLYkFojN5CbM9h++ZTZtknA==}
|
||||||
|
engines: {node: '>=8', npm: '>=5'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
nanoid@3.3.11:
|
nanoid@3.3.11:
|
||||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
@@ -1005,6 +1062,19 @@ packages:
|
|||||||
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
|
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
pinia@3.0.2:
|
||||||
|
resolution: {integrity: sha512-sH2JK3wNY809JOeiiURUR0wehJ9/gd9qFN2Y828jCbxEzKEmEt0pzCXwqiSTfuRsK9vQsOflSdnbdBOGrhtn+g==}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.4.4'
|
||||||
|
vue: ^2.7.0 || ^3.5.11
|
||||||
|
peerDependenciesMeta:
|
||||||
|
typescript:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
pkcs7@1.0.4:
|
||||||
|
resolution: {integrity: sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
postcss@8.5.3:
|
postcss@8.5.3:
|
||||||
resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
|
resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
@@ -1013,12 +1083,19 @@ packages:
|
|||||||
resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==}
|
resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
process@0.11.10:
|
||||||
|
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
||||||
|
engines: {node: '>= 0.6.0'}
|
||||||
|
|
||||||
promise-polyfill@8.3.0:
|
promise-polyfill@8.3.0:
|
||||||
resolution: {integrity: sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==}
|
resolution: {integrity: sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==}
|
||||||
|
|
||||||
proxy-from-env@1.1.0:
|
proxy-from-env@1.1.0:
|
||||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||||
|
|
||||||
|
regenerator-runtime@0.14.1:
|
||||||
|
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
||||||
|
|
||||||
rfdc@1.4.1:
|
rfdc@1.4.1:
|
||||||
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
|
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
|
||||||
|
|
||||||
@@ -1239,6 +1316,21 @@ packages:
|
|||||||
varint@6.0.0:
|
varint@6.0.0:
|
||||||
resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==}
|
resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==}
|
||||||
|
|
||||||
|
video.js@8.22.0:
|
||||||
|
resolution: {integrity: sha512-xge2kpjsvC0zgFJ1cqt+wTqsi21+huFswlonPFh7qiplypsb4FN/D2Rz6bWdG/S9eQaPHfWHsarmJL/7D3DHoA==}
|
||||||
|
|
||||||
|
videojs-contrib-quality-levels@4.1.0:
|
||||||
|
resolution: {integrity: sha512-TfrXJJg1Bv4t6TOCMEVMwF/CoS8iENYsWNKip8zfhB5kTcegiFYezEA0eHAJPU64ZC8NQbxQgOwAsYU8VXbOWA==}
|
||||||
|
engines: {node: '>=16', npm: '>=8'}
|
||||||
|
peerDependencies:
|
||||||
|
video.js: ^8
|
||||||
|
|
||||||
|
videojs-font@4.2.0:
|
||||||
|
resolution: {integrity: sha512-YPq+wiKoGy2/M7ccjmlvwi58z2xsykkkfNMyIg4xb7EZQQNwB71hcSsB3o75CqQV7/y5lXkXhI/rsGAS7jfEmQ==}
|
||||||
|
|
||||||
|
videojs-vtt.js@0.15.5:
|
||||||
|
resolution: {integrity: sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==}
|
||||||
|
|
||||||
vite-hot-client@2.0.4:
|
vite-hot-client@2.0.4:
|
||||||
resolution: {integrity: sha512-W9LOGAyGMrbGArYJN4LBCdOC5+Zwh7dHvOHC0KmGKkJhsOzaKbpo/jEjpPKVHIW0/jBWj8RZG0NUxfgA8BxgAg==}
|
resolution: {integrity: sha512-W9LOGAyGMrbGArYJN4LBCdOC5+Zwh7dHvOHC0KmGKkJhsOzaKbpo/jEjpPKVHIW0/jBWj8RZG0NUxfgA8BxgAg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1519,6 +1611,10 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@babel/runtime@7.27.0':
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime: 0.14.1
|
||||||
|
|
||||||
'@babel/template@7.27.0':
|
'@babel/template@7.27.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.26.2
|
'@babel/code-frame': 7.26.2
|
||||||
@@ -1739,6 +1835,28 @@ snapshots:
|
|||||||
|
|
||||||
'@types/web-bluetooth@0.0.16': {}
|
'@types/web-bluetooth@0.0.16': {}
|
||||||
|
|
||||||
|
'@videojs/http-streaming@3.17.0(video.js@8.22.0)':
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.0
|
||||||
|
'@videojs/vhs-utils': 4.1.1
|
||||||
|
aes-decrypter: 4.0.2
|
||||||
|
global: 4.4.0
|
||||||
|
m3u8-parser: 7.2.0
|
||||||
|
mpd-parser: 1.3.1
|
||||||
|
mux.js: 7.1.0
|
||||||
|
video.js: 8.22.0
|
||||||
|
|
||||||
|
'@videojs/vhs-utils@4.1.1':
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.0
|
||||||
|
global: 4.4.0
|
||||||
|
|
||||||
|
'@videojs/xhr@2.7.0':
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.0
|
||||||
|
global: 4.4.0
|
||||||
|
is-function: 1.0.2
|
||||||
|
|
||||||
'@vitejs/plugin-vue@5.2.3(vite@6.3.0(sass-embedded@1.86.3))(vue@3.5.13)':
|
'@vitejs/plugin-vue@5.2.3(vite@6.3.0(sass-embedded@1.86.3))(vue@3.5.13)':
|
||||||
dependencies:
|
dependencies:
|
||||||
vite: 6.3.0(sass-embedded@1.86.3)
|
vite: 6.3.0(sass-embedded@1.86.3)
|
||||||
@@ -1805,6 +1923,10 @@ snapshots:
|
|||||||
|
|
||||||
'@vue/devtools-api@6.6.4': {}
|
'@vue/devtools-api@6.6.4': {}
|
||||||
|
|
||||||
|
'@vue/devtools-api@7.7.5':
|
||||||
|
dependencies:
|
||||||
|
'@vue/devtools-kit': 7.7.5
|
||||||
|
|
||||||
'@vue/devtools-core@7.7.5(vite@6.3.0(sass-embedded@1.86.3))(vue@3.5.13)':
|
'@vue/devtools-core@7.7.5(vite@6.3.0(sass-embedded@1.86.3))(vue@3.5.13)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vue/devtools-kit': 7.7.5
|
'@vue/devtools-kit': 7.7.5
|
||||||
@@ -1874,6 +1996,15 @@ snapshots:
|
|||||||
- '@vue/composition-api'
|
- '@vue/composition-api'
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
|
'@xmldom/xmldom@0.8.10': {}
|
||||||
|
|
||||||
|
aes-decrypter@4.0.2:
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.0
|
||||||
|
'@videojs/vhs-utils': 4.1.1
|
||||||
|
global: 4.4.0
|
||||||
|
pkcs7: 1.0.4
|
||||||
|
|
||||||
async-validator@4.2.5: {}
|
async-validator@4.2.5: {}
|
||||||
|
|
||||||
asynckit@0.4.0: {}
|
asynckit@0.4.0: {}
|
||||||
@@ -1957,6 +2088,8 @@ snapshots:
|
|||||||
|
|
||||||
depd@2.0.0: {}
|
depd@2.0.0: {}
|
||||||
|
|
||||||
|
dom-walk@0.1.2: {}
|
||||||
|
|
||||||
dplayer@1.27.1:
|
dplayer@1.27.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
axios: 1.2.3
|
axios: 1.2.3
|
||||||
@@ -2122,6 +2255,11 @@ snapshots:
|
|||||||
'@sec-ant/readable-stream': 0.4.1
|
'@sec-ant/readable-stream': 0.4.1
|
||||||
is-stream: 4.0.1
|
is-stream: 4.0.1
|
||||||
|
|
||||||
|
global@4.4.0:
|
||||||
|
dependencies:
|
||||||
|
min-document: 2.19.0
|
||||||
|
process: 0.11.10
|
||||||
|
|
||||||
globals@11.12.0: {}
|
globals@11.12.0: {}
|
||||||
|
|
||||||
gopd@1.2.0: {}
|
gopd@1.2.0: {}
|
||||||
@@ -2150,6 +2288,8 @@ snapshots:
|
|||||||
|
|
||||||
is-docker@3.0.0: {}
|
is-docker@3.0.0: {}
|
||||||
|
|
||||||
|
is-function@1.0.2: {}
|
||||||
|
|
||||||
is-inside-container@1.0.0:
|
is-inside-container@1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-docker: 3.0.0
|
is-docker: 3.0.0
|
||||||
@@ -2198,6 +2338,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yallist: 3.1.1
|
yallist: 3.1.1
|
||||||
|
|
||||||
|
m3u8-parser@7.2.0:
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.0
|
||||||
|
'@videojs/vhs-utils': 4.1.1
|
||||||
|
global: 4.4.0
|
||||||
|
|
||||||
magic-string@0.30.17:
|
magic-string@0.30.17:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.0
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
@@ -2212,12 +2358,28 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mime-db: 1.52.0
|
mime-db: 1.52.0
|
||||||
|
|
||||||
|
min-document@2.19.0:
|
||||||
|
dependencies:
|
||||||
|
dom-walk: 0.1.2
|
||||||
|
|
||||||
mitt@3.0.1: {}
|
mitt@3.0.1: {}
|
||||||
|
|
||||||
|
mpd-parser@1.3.1:
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.0
|
||||||
|
'@videojs/vhs-utils': 4.1.1
|
||||||
|
'@xmldom/xmldom': 0.8.10
|
||||||
|
global: 4.4.0
|
||||||
|
|
||||||
mrmime@2.0.1: {}
|
mrmime@2.0.1: {}
|
||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|
||||||
|
mux.js@7.1.0:
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.0
|
||||||
|
global: 4.4.0
|
||||||
|
|
||||||
nanoid@3.3.11: {}
|
nanoid@3.3.11: {}
|
||||||
|
|
||||||
nanoid@5.1.5: {}
|
nanoid@5.1.5: {}
|
||||||
@@ -2256,6 +2418,15 @@ snapshots:
|
|||||||
|
|
||||||
picomatch@4.0.2: {}
|
picomatch@4.0.2: {}
|
||||||
|
|
||||||
|
pinia@3.0.2(vue@3.5.13):
|
||||||
|
dependencies:
|
||||||
|
'@vue/devtools-api': 7.7.5
|
||||||
|
vue: 3.5.13
|
||||||
|
|
||||||
|
pkcs7@1.0.4:
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.0
|
||||||
|
|
||||||
postcss@8.5.3:
|
postcss@8.5.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.3.11
|
nanoid: 3.3.11
|
||||||
@@ -2266,10 +2437,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
parse-ms: 4.0.0
|
parse-ms: 4.0.0
|
||||||
|
|
||||||
|
process@0.11.10: {}
|
||||||
|
|
||||||
promise-polyfill@8.3.0: {}
|
promise-polyfill@8.3.0: {}
|
||||||
|
|
||||||
proxy-from-env@1.1.0: {}
|
proxy-from-env@1.1.0: {}
|
||||||
|
|
||||||
|
regenerator-runtime@0.14.1: {}
|
||||||
|
|
||||||
rfdc@1.4.1: {}
|
rfdc@1.4.1: {}
|
||||||
|
|
||||||
rollup@4.40.0:
|
rollup@4.40.0:
|
||||||
@@ -2463,6 +2638,32 @@ snapshots:
|
|||||||
|
|
||||||
varint@6.0.0: {}
|
varint@6.0.0: {}
|
||||||
|
|
||||||
|
video.js@8.22.0:
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.27.0
|
||||||
|
'@videojs/http-streaming': 3.17.0(video.js@8.22.0)
|
||||||
|
'@videojs/vhs-utils': 4.1.1
|
||||||
|
'@videojs/xhr': 2.7.0
|
||||||
|
aes-decrypter: 4.0.2
|
||||||
|
global: 4.4.0
|
||||||
|
m3u8-parser: 7.2.0
|
||||||
|
mpd-parser: 1.3.1
|
||||||
|
mux.js: 7.1.0
|
||||||
|
videojs-contrib-quality-levels: 4.1.0(video.js@8.22.0)
|
||||||
|
videojs-font: 4.2.0
|
||||||
|
videojs-vtt.js: 0.15.5
|
||||||
|
|
||||||
|
videojs-contrib-quality-levels@4.1.0(video.js@8.22.0):
|
||||||
|
dependencies:
|
||||||
|
global: 4.4.0
|
||||||
|
video.js: 8.22.0
|
||||||
|
|
||||||
|
videojs-font@4.2.0: {}
|
||||||
|
|
||||||
|
videojs-vtt.js@0.15.5:
|
||||||
|
dependencies:
|
||||||
|
global: 4.4.0
|
||||||
|
|
||||||
vite-hot-client@2.0.4(vite@6.3.0(sass-embedded@1.86.3)):
|
vite-hot-client@2.0.4(vite@6.3.0(sass-embedded@1.86.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
vite: 6.3.0(sass-embedded@1.86.3)
|
vite: 6.3.0(sass-embedded@1.86.3)
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 66 KiB |
@@ -1,7 +1,8 @@
|
|||||||
import request from "./request";
|
import request from "./request";
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
const nbaapi = axios.create({
|
const nbaapi = axios.create({
|
||||||
baseURL: 'http://localhost:9005/api',
|
baseURL: 'http://api.new9.me/api',
|
||||||
// baseURL: 'http://110.42.255.182:8080',
|
// baseURL: 'http://110.42.255.182:8080',
|
||||||
timeout: 2000,
|
timeout: 2000,
|
||||||
})
|
})
|
||||||
@@ -11,30 +12,73 @@ const urls = async () => {
|
|||||||
url: '/urls',
|
url: '/urls',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
// console.log(response.data); // 可选:调试用
|
return response.data;
|
||||||
return response.data; // 返回数据
|
})
|
||||||
})
|
.catch((error) => {
|
||||||
.catch((error) => {
|
console.error('获取直播URL失败:', error);
|
||||||
console.error('获取直播URL失败:', error);
|
throw error;
|
||||||
throw error; // 可以选择抛出错误或返回默认值,比如 return []
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const games = () => {
|
const games = async () => {
|
||||||
nbaapi({
|
return await nbaapi({
|
||||||
url: '/games',
|
url: '/games',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
}).then((response) => {
|
})
|
||||||
console.log(response.data);
|
.then((response) => {
|
||||||
})
|
// console.log(response.data); // 调试用
|
||||||
}
|
return response.data; // 确保返回数据
|
||||||
const schedule =(params) => {
|
})
|
||||||
return request({
|
.catch((error) => {
|
||||||
url: '/game/schedule',
|
console.error('获取赛事数据失败:', error);
|
||||||
method: 'get',
|
throw error; // 或者返回空数组 return []
|
||||||
params: params,
|
});
|
||||||
});
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export {schedule,games,urls};
|
const go = async (pwd) => {
|
||||||
|
return await nbaapi({
|
||||||
|
url: '/go',
|
||||||
|
method: 'get',
|
||||||
|
params: {
|
||||||
|
// 这里可以添加请求参数
|
||||||
|
pwd: pwd,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
// console.log(response.data); // 调试用
|
||||||
|
return response.data; // 确保返回数据
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('获取赛事数据失败:', error);
|
||||||
|
throw error; // 或者返回空数组 return []
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const schedule = (params) => {
|
||||||
|
return request({
|
||||||
|
url: '/game/schedule',
|
||||||
|
method: 'get',
|
||||||
|
params: params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const addUrls = async (gameId, urls) => {
|
||||||
|
return await nbaapi({
|
||||||
|
url: '/addUrls',
|
||||||
|
method: 'post',
|
||||||
|
data: {
|
||||||
|
gameId: gameId,
|
||||||
|
urls: urls
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
return response.data;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('添加直播URL失败:', error);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export { schedule, games, urls, go,addUrls };
|
||||||
@@ -1,196 +1,296 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="video-container">
|
<div class="live-stream-container">
|
||||||
<div
|
<!-- 比赛信息 -->
|
||||||
v-for="(video, index) in videoList"
|
<div class="game-header" v-if="gameData">
|
||||||
:key="index"
|
<div class="team-info away-team">
|
||||||
class="video-card"
|
<img :src="gameData.awayTeam.logo" :alt="gameData.awayTeam.name" />
|
||||||
@click="handleCardClick(index)"
|
<div class="team-details">
|
||||||
>
|
<h3>{{ gameData.awayTeam.city }}</h3>
|
||||||
<div class="video-wrapper">
|
<p>{{ gameData.awayTeam.name }}</p>
|
||||||
<div :id="'dplayer-' + index" class="dplayer-container"></div>
|
<span>{{ gameData.awayTeam.record }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="video-info">
|
|
||||||
<h3 class="video-title">{{ video.title }}</h3>
|
<div class="vs-circle">VS</div>
|
||||||
<p class="video-desc">{{ video.description }}</p>
|
|
||||||
|
<div class="team-info home-team">
|
||||||
|
<img :src="gameData.homeTeam.logo" :alt="gameData.homeTeam.name" />
|
||||||
|
<div class="team-details">
|
||||||
|
<h3>{{ gameData.homeTeam.city }}</h3>
|
||||||
|
<p>{{ gameData.homeTeam.name }}</p>
|
||||||
|
<span>{{ gameData.homeTeam.record }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 播放器 -->
|
||||||
|
<div id="dplayer-live" class="dplayer-container"></div>
|
||||||
|
|
||||||
|
<!-- 直播源列表(始终显示) -->
|
||||||
|
<div class="stream-switcher" v-if="allStreams.length > 0">
|
||||||
|
<!-- <h3>直播源</h3> -->
|
||||||
|
<div class="stream-buttons">
|
||||||
|
<button
|
||||||
|
v-for="stream in allStreams"
|
||||||
|
:key="stream.type"
|
||||||
|
@click="switchStream(stream)"
|
||||||
|
:class="{ active: currentStream?.type === stream.type }"
|
||||||
|
>
|
||||||
|
{{ getStreamName(stream.type) }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 返回按钮 -->
|
||||||
|
<button class="back-button" @click="goBack">
|
||||||
|
← 返回赛程
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
import { ref, onMounted, onBeforeUnmount, computed } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useGameStore } from '@/stores/game'
|
||||||
import DPlayer from 'dplayer'
|
import DPlayer from 'dplayer'
|
||||||
import Hls from 'hls.js'
|
import Hls from 'hls.js'
|
||||||
import Flv from 'flv.js'
|
import Flv from 'flv.js'
|
||||||
// 将 flv.js 注册为全局变量
|
|
||||||
|
// 注册全局变量
|
||||||
window.flvjs = Flv
|
window.flvjs = Flv
|
||||||
window.Hls = Hls
|
window.Hls = Hls
|
||||||
// 视频列表数据
|
|
||||||
const videoList = ref([
|
|
||||||
{
|
|
||||||
title: '示例视频1',
|
|
||||||
description: '这是一个示例视频描述',
|
|
||||||
// url: './../../public/videos/7.mp4',
|
|
||||||
url: 'https://feijing-xzbonlinepull.bszb.me/live/202_3520771_1.m3u8?txSecret=92b9a5df1d71b2a1dbf4cb41e7c7b507&txTime=68011a9d',
|
|
||||||
// pic: 'https://example.com/poster1.jpg'
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// title: '示例视频2',
|
|
||||||
// description: '这是另一个示例视频描述',
|
|
||||||
// url: 'https://example.com/video2.mp4',
|
|
||||||
// pic: 'https://example.com/poster2.jpg'
|
|
||||||
// },
|
|
||||||
// 可以添加更多视频
|
|
||||||
])
|
|
||||||
|
|
||||||
const dpInstances = ref([])
|
const router = useRouter()
|
||||||
|
const gameStore = useGameStore()
|
||||||
|
|
||||||
|
const dpInstance = ref(null)
|
||||||
|
|
||||||
|
// 从store获取数据
|
||||||
|
const gameData = computed(() => gameStore.currentGame)
|
||||||
|
const allStreams = computed(() => gameStore.allStreams)
|
||||||
|
const currentStream = computed({
|
||||||
|
get: () => gameStore.currentStream,
|
||||||
|
set: (val) => gameStore.currentStream = val
|
||||||
|
})
|
||||||
|
|
||||||
// 初始化播放器
|
// 初始化播放器
|
||||||
const initDPlayers = () => {
|
const initPlayer = () => {
|
||||||
videoList.value.forEach((video, index) => {
|
if (dpInstance.value) {
|
||||||
const dp = new DPlayer({
|
dpInstance.value.destroy();
|
||||||
container: document.getElementById(`dplayer-${index}`),
|
}
|
||||||
// live: true,
|
|
||||||
screenshot: true,
|
|
||||||
autoplay: true,
|
|
||||||
theme: '#b7daff',
|
|
||||||
loop: false,
|
|
||||||
lang: 'zh-cn',
|
|
||||||
hotkey: true,
|
|
||||||
preload: 'auto',
|
|
||||||
volume: 0.6,
|
|
||||||
video: {
|
|
||||||
url: video.url,
|
|
||||||
pic: video.pic,
|
|
||||||
thumbnails: video.pic,
|
|
||||||
type: 'auto',
|
|
||||||
},
|
|
||||||
pluginOptions:{
|
|
||||||
hls: {
|
|
||||||
// 这里可以添加 HLS.js 的配置选项
|
|
||||||
debug: true,
|
|
||||||
enableWorker: true,
|
|
||||||
manifestLoadingTimeOut: 10000,
|
|
||||||
levelLoadingTimeOut: 10000,
|
|
||||||
},
|
|
||||||
flv: {
|
|
||||||
// 这里可以添加 FLV.js 的配置选项
|
|
||||||
enableWorker: true,
|
|
||||||
enableStashBuffer: true,
|
|
||||||
stashInitialSize: 128,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
console.log(dp.plugins.flv); // flv 实例
|
|
||||||
// 监听播放器的事件
|
|
||||||
dpInstances.value.push(dp)
|
|
||||||
|
|
||||||
})
|
dpInstance.value = new DPlayer({
|
||||||
|
container: document.getElementById('dplayer-live'),
|
||||||
|
live: true,
|
||||||
|
autoplay: true,
|
||||||
|
video: {
|
||||||
|
url: currentStream.value?.url || '',
|
||||||
|
type: 'auto'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 强制设置视频尺寸
|
||||||
|
setTimeout(() => {
|
||||||
|
const container = document.getElementById('dplayer-live');
|
||||||
|
const video = container?.querySelector('video');
|
||||||
|
if (video) {
|
||||||
|
video.style.cssText = `
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
object-fit: contain !important;
|
||||||
|
position: absolute !important;
|
||||||
|
top: 0 !important;
|
||||||
|
left: 0 !important;
|
||||||
|
`;
|
||||||
|
// console.log('视频实际尺寸:', video.videoWidth, 'x', video.videoHeight);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换直播源
|
||||||
|
const switchStream = (stream) => {
|
||||||
|
currentStream.value = stream
|
||||||
|
initPlayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取直播源名称
|
||||||
|
const getStreamName = (type) => {
|
||||||
|
const names = {
|
||||||
|
tx: '企鹅体育',
|
||||||
|
wl: '纬来体育',
|
||||||
|
mg: '咪咕体育',
|
||||||
|
nba: '高清原声',
|
||||||
|
zb: '高清直播'
|
||||||
|
}
|
||||||
|
return names[type] || type
|
||||||
|
}
|
||||||
|
|
||||||
// 处理卡片点击事件
|
// 返回赛程页
|
||||||
const handleCardClick = (index) => {
|
const goBack = () => {
|
||||||
// 暂停所有其他播放器
|
gameStore.clearGameData()
|
||||||
dpInstances.value.forEach((dp, i) => {
|
router.go(-1)
|
||||||
if (i !== index && !dp.video.paused) {
|
|
||||||
dp.pause()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initDPlayers()
|
// 默认选择第一个直播源
|
||||||
|
if (allStreams.value.length > 0 && !currentStream.value) {
|
||||||
|
currentStream.value = allStreams.value[0]
|
||||||
|
}
|
||||||
|
initPlayer()
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
// 销毁所有播放器实例
|
if (dpInstance.value) {
|
||||||
dpInstances.value.forEach(dp => {
|
dpInstance.value.destroy()
|
||||||
dp.destroy()
|
}
|
||||||
})
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.video-container {
|
.live-stream-container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: 40px 0;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
// align-items: center;
|
align-items: center;
|
||||||
|
gap: 40px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
.video-card {
|
.team-info {
|
||||||
flex: 1 1 calc(33.333% - 20px);
|
display: flex;
|
||||||
min-width: 768px;
|
align-items: center;
|
||||||
max-width: 70%;
|
gap: 15px;
|
||||||
background: #d6f3f0;
|
|
||||||
border-radius: 8px;
|
img {
|
||||||
overflow: hidden;
|
width: 80px;
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
height: 80px;
|
||||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-details {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 5px 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs-circle {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
background: #e74c3c;
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dplayer-container {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
position: relative;
|
||||||
|
background: #000;
|
||||||
|
|
||||||
|
::v-deep {
|
||||||
|
.dplayer-video {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
object-fit: contain !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream-switcher {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
// transform: translateY(-5px);
|
background: #e0e0e0;
|
||||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-wrapper {
|
&.active {
|
||||||
position: relative;
|
background: #3498db;
|
||||||
padding-top: 56.25%; /* 16:9 宽高比 */
|
color: white;
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.dplayer-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-info {
|
|
||||||
padding: 15px;
|
|
||||||
|
|
||||||
.video-title {
|
|
||||||
margin: 0 0 8px 0;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
color: #333;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-desc {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #666;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 移动端适配 */
|
.back-button {
|
||||||
|
display: block;
|
||||||
|
width: 200px;
|
||||||
|
margin: 30px auto 0;
|
||||||
|
padding: 12px 24px;
|
||||||
|
background: #3498db;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #2980b9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.video-container {
|
.game-header {
|
||||||
.video-card {
|
flex-direction: column;
|
||||||
flex: 1 1 calc(50% - 15px);
|
gap: 20px;
|
||||||
min-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
.vs-circle {
|
||||||
@media (max-width: 480px) {
|
margin: 10px 0;
|
||||||
.video-container {
|
}
|
||||||
|
|
||||||
.video-card {
|
.back-button {
|
||||||
flex: 1 1 100%;
|
width: 100%;
|
||||||
min-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -2,12 +2,12 @@
|
|||||||
<div class="nba-schedule-container">
|
<div class="nba-schedule-container">
|
||||||
<!-- 赞助商信息 -->
|
<!-- 赞助商信息 -->
|
||||||
<div v-if="scheduleData?.data?.sponsor" class="sponsor-banner">
|
<div v-if="scheduleData?.data?.sponsor" class="sponsor-banner">
|
||||||
<span>所有内容均来源互联网,如有侵权联系邮箱:xdd9@vip.qq.com</span>
|
<span>所有内容均来源互联网,有问题请联系邮箱:xdd9@vip.qq.com</span>
|
||||||
<img
|
<!-- <img
|
||||||
:src="scheduleData.data.sponsor.logo"
|
:src="scheduleData.data.sponsor.logo"
|
||||||
:alt="scheduleData.data.sponsor.name"
|
:alt="scheduleData.data.sponsor.name"
|
||||||
class="sponsor-logo"
|
class="sponsor-logo"
|
||||||
/>
|
/> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 赛程日期导航 -->
|
<!-- 赛程日期导航 -->
|
||||||
@@ -49,7 +49,10 @@
|
|||||||
<!-- 客队信息 -->
|
<!-- 客队信息 -->
|
||||||
<div
|
<div
|
||||||
class="team away-team"
|
class="team away-team"
|
||||||
:class="{ 'tbd-team': !game.teamValid }"
|
:class="{
|
||||||
|
'tbd-team': !game.teamValid,
|
||||||
|
winner: isWinner(game, 'away'), // 添加判断是否为胜者
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="game.awayTeamLogoDark"
|
:src="game.awayTeamLogoDark"
|
||||||
@@ -91,7 +94,10 @@
|
|||||||
<!-- 主队信息 -->
|
<!-- 主队信息 -->
|
||||||
<div
|
<div
|
||||||
class="team home-team"
|
class="team home-team"
|
||||||
:class="{ 'tbd-team': !game.teamValid }"
|
:class="{
|
||||||
|
'tbd-team': !game.teamValid,
|
||||||
|
winner: isWinner(game, 'home'), // 添加判断是否为胜者
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="game.homeTeamLogoDark"
|
:src="game.homeTeamLogoDark"
|
||||||
@@ -115,25 +121,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 直播间按钮区域(仅当天进行中的比赛显示) -->
|
<div class="live-buttons">
|
||||||
<div class="live-buttons" v-if="shouldShowLiveArea(game)">
|
<!-- 只有当比赛未结束且是当天比赛时才显示直播区域 -->
|
||||||
<template v-if="game.status === 2 && hasLiveStreams(game.gameId)">
|
<template v-if="game.status !== 3 && shouldShowLiveArea(game)">
|
||||||
<button
|
<template v-if="hasLiveStreams(game.gameId)">
|
||||||
v-for="stream in getLiveStreams(game.gameId)"
|
<!-- 直播按钮 -->
|
||||||
:key="stream.type"
|
<button
|
||||||
class="live-btn"
|
v-for="stream in getLiveStreams(game.gameId)"
|
||||||
:class="{
|
:key="stream.type"
|
||||||
primary: stream.type === 'qq',
|
@click="goToLive(game, stream)"
|
||||||
secondary: stream.type !== 'qq',
|
class="live-btn"
|
||||||
}"
|
>
|
||||||
@click="goToLive(stream.url)"
|
<span class="btn-icon">📺</span>
|
||||||
>
|
{{ getStreamName(stream.type) }}
|
||||||
<span class="btn-icon">📺</span>
|
</button>
|
||||||
<span>{{ getStreamName(stream.type) }}</span>
|
</template>
|
||||||
</button>
|
<div v-else class="no-live">无直播信号</div>
|
||||||
</template>
|
</template>
|
||||||
<div v-else-if="game.status === 1" class="no-live">未开始</div>
|
<div v-else-if="game.status === 3" class="no-live">
|
||||||
<div v-else class="no-live">无直播信号</div>
|
比赛已结束
|
||||||
|
</div>
|
||||||
|
<div v-else class="no-live">未开始</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 比赛场地和赛季信息 -->
|
<!-- 比赛场地和赛季信息 -->
|
||||||
@@ -162,7 +170,8 @@ import { computed, ref } from "vue";
|
|||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { urls } from "@/api/nba";
|
import { urls } from "@/api/nba";
|
||||||
import { onMounted } from "vue";
|
import { onMounted } from "vue";
|
||||||
|
import { useGameStore } from "@/stores/game";
|
||||||
|
const gameStore = useGameStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const urlsData = ref([]);
|
const urlsData = ref([]);
|
||||||
|
|
||||||
@@ -170,22 +179,21 @@ const shouldShowLiveArea = (game) => {
|
|||||||
// 1. 已结束的比赛不显示
|
// 1. 已结束的比赛不显示
|
||||||
if (game.status === 3) return false;
|
if (game.status === 3) return false;
|
||||||
|
|
||||||
const gameDate = new Date(game.dateTimeUtc);
|
// 2. 获取今天的日期(北京时间)
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
|
const todayStr = `${today.getFullYear()}-${(today.getMonth() + 1)
|
||||||
|
.toString()
|
||||||
|
.padStart(2, "0")}-${today.getDate().toString().padStart(2, "0")}`;
|
||||||
|
|
||||||
// 2. 只显示当天及未来的比赛
|
// 3. 直接比较 startDate(已经是北京时间)
|
||||||
// 清除时间部分,只比较日期
|
return game.startDate === todayStr;
|
||||||
today.setHours(0, 0, 0, 0);
|
|
||||||
gameDate.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
return gameDate >= today;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
const response = await urls();
|
const response = await urls();
|
||||||
urlsData.value = response || [];
|
urlsData.value = response || [];
|
||||||
// console.log("获取的直播URL:", urlsData.value);
|
// console.log("获取的直播URL数据:", urlsData.value); // 检查数据是否正确
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("获取直播URL失败:", err);
|
console.error("获取直播URL失败:", err);
|
||||||
urlsData.value = [];
|
urlsData.value = [];
|
||||||
@@ -205,25 +213,31 @@ const isLiveGame = (game) => {
|
|||||||
const hasLiveStreams = (gameId) => {
|
const hasLiveStreams = (gameId) => {
|
||||||
if (!urlsData.value || !gameId) return false;
|
if (!urlsData.value || !gameId) return false;
|
||||||
|
|
||||||
// 查找匹配的gameId
|
// 遍历所有直播流数据
|
||||||
const gameStreams = urlsData.value.find((item) => item[gameId]);
|
for (const streamGroup of urlsData.value) {
|
||||||
return !!gameStreams;
|
if (streamGroup[gameId]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取比赛的直播流
|
// 获取比赛的直播流
|
||||||
const getLiveStreams = (gameId) => {
|
const getLiveStreams = (gameId) => {
|
||||||
if (!urlsData.value || !gameId) return [];
|
const id = String(gameId); // 转为字符串
|
||||||
|
for (const streamGroup of urlsData.value) {
|
||||||
const gameStreams = urlsData.value.find((item) => item[gameId]);
|
if (streamGroup[id]) return streamGroup[id];
|
||||||
return gameStreams ? gameStreams[gameId] : [];
|
}
|
||||||
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取流名称
|
// 获取流名称
|
||||||
const getStreamName = (type) => {
|
const getStreamName = (type) => {
|
||||||
const names = {
|
const names = {
|
||||||
tx: "TX直播",
|
tx: "企鹅体育",
|
||||||
wl: "纬来直播",
|
wl: "纬来体育",
|
||||||
nba: "原声直播",
|
nba: "高清原声",
|
||||||
|
mg: "咪咕体育",
|
||||||
zb: "高清直播",
|
zb: "高清直播",
|
||||||
// 可以添加更多类型
|
// 可以添加更多类型
|
||||||
};
|
};
|
||||||
@@ -231,14 +245,39 @@ const getStreamName = (type) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 跳转到直播页面
|
// 跳转到直播页面
|
||||||
const goToLive = (url) => {
|
const goToLive = (game, stream) => {
|
||||||
if (!url) return;
|
// 准备比赛数据
|
||||||
|
const gameData = {
|
||||||
|
homeTeam: {
|
||||||
|
name: game.homeTeamName,
|
||||||
|
logo: game.homeTeamLogoDark,
|
||||||
|
city: game.homeTeamCity,
|
||||||
|
record: `${game.homeTeamWins}胜-${game.homeTeamLosses}负`,
|
||||||
|
},
|
||||||
|
awayTeam: {
|
||||||
|
name: game.awayTeamName,
|
||||||
|
logo: game.awayTeamLogoDark,
|
||||||
|
city: game.awayTeamCity,
|
||||||
|
record: `${game.awayTeamWins}胜-${game.awayTeamLosses}负`,
|
||||||
|
},
|
||||||
|
gameInfo: {
|
||||||
|
arena: game.arenaName,
|
||||||
|
season: game.seasonName,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 存储到Pinia
|
||||||
|
gameStore.setCurrentGame({
|
||||||
|
gameData,
|
||||||
|
currentStream: stream,
|
||||||
|
allStreams: getLiveStreams(game.gameId),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 导航到播放页
|
||||||
router.push({
|
router.push({
|
||||||
name: "Play",
|
name: "Play",
|
||||||
query: {
|
params: {
|
||||||
url: url,
|
gameId: game.gameId,
|
||||||
// 其他参数...
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -324,6 +363,19 @@ const changeDate = (direction) => {
|
|||||||
emit("dateChange", date);
|
emit("dateChange", date);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 判断某支球队是否是胜者
|
||||||
|
const isWinner = (game, teamType) => {
|
||||||
|
// 如果比赛未结束,没有胜者
|
||||||
|
if (game.status !== 3) return false;
|
||||||
|
|
||||||
|
// 比较比分
|
||||||
|
if (teamType === 'away') {
|
||||||
|
return game.awayTeamScore > game.homeTeamScore;
|
||||||
|
} else {
|
||||||
|
return game.homeTeamScore > game.awayTeamScore;
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -520,8 +572,8 @@ const changeDate = (direction) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.game-not-started {
|
.game-not-started {
|
||||||
color: #6c757d;
|
color: #5a7cec;
|
||||||
font-size: 16px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.game-in-progress {
|
.game-in-progress {
|
||||||
@@ -693,4 +745,20 @@ const changeDate = (direction) => {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
|
/* 胜者背景色 */
|
||||||
|
.team.winner {
|
||||||
|
background-color: rgba(76, 175, 80, 0.1); /* 浅绿色背景 */
|
||||||
|
border-left: 3px solid #74fd79; /* 左侧绿色边框 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 如果希望更明显的效果,可以调整样式 */
|
||||||
|
.team.winner .team-name {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2E7D32; /* 深绿色文字 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.team.winner .team-score {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2E7D32; /* 深绿色比分 */
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import router from './router'
|
import router from './router'
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
app.use(ElementPlus)
|
||||||
|
app.use(createPinia())
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import IndexVue from '@/views/Index.vue'
|
import IndexVue from '@/views/Index.vue'
|
||||||
import PlayVue from '@/views/Play.vue'
|
import PlayVue from '@/views/Play.vue'
|
||||||
|
import AdminVue from '@/views/Admin.vue'
|
||||||
|
import { useGameStore } from '@/stores/game'
|
||||||
|
import TestVue from '@/views/Test.vue'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -10,10 +14,25 @@ const routes = [
|
|||||||
props: route => ({ query: route.query })
|
props: route => ({ query: route.query })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/play',
|
path: '/play/:gameId',
|
||||||
name: 'Play',
|
name: 'Play',
|
||||||
component: PlayVue,
|
component: () => import('@/views/Play.vue'),
|
||||||
props: route => ({ query: route.query })
|
props: true // 启用props接收路由参数
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/lives',
|
||||||
|
name: 'Admin',
|
||||||
|
component: AdminVue,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/test',
|
||||||
|
name: 'Test',
|
||||||
|
component: TestVue,
|
||||||
|
},
|
||||||
|
// 添加通配符路由,捕获所有未匹配的路径
|
||||||
|
{
|
||||||
|
path: '/:pathMatch(.*)*',
|
||||||
|
redirect: '/'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -22,4 +41,19 @@ const router = createRouter({
|
|||||||
routes
|
routes
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 添加全局路由守卫
|
||||||
|
router.beforeEach((to, from) => {
|
||||||
|
const gameStore = useGameStore()
|
||||||
|
|
||||||
|
// 离开播放页时清理数据
|
||||||
|
if (from.name === 'Play' && to.name !== 'Play') {
|
||||||
|
gameStore.clearGameData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 进入播放页时检查数据
|
||||||
|
if (to.name === 'Play' && !gameStore.currentGame) {
|
||||||
|
return '/' // 无数据则重定向到首页
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
39
src/stores/game.js
Normal file
39
src/stores/game.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
export const useGameStore = defineStore('game', () => {
|
||||||
|
// 状态
|
||||||
|
const _currentGame = ref(null)
|
||||||
|
const _currentStream = ref(null)
|
||||||
|
const _allStreams = ref([])
|
||||||
|
|
||||||
|
// 计算属性(保持响应式)
|
||||||
|
const currentGame = computed(() => _currentGame.value)
|
||||||
|
const allStreams = computed(() => _allStreams.value)
|
||||||
|
const currentStream = computed({
|
||||||
|
get: () => _currentStream.value,
|
||||||
|
set: (val) => _currentStream.value = val // 确保可写
|
||||||
|
})
|
||||||
|
|
||||||
|
// 设置当前比赛
|
||||||
|
const setCurrentGame = (data) => {
|
||||||
|
_currentGame.value = data.gameData
|
||||||
|
_currentStream.value = data.currentStream
|
||||||
|
_allStreams.value = data.allStreams
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除数据
|
||||||
|
const clearGameData = () => {
|
||||||
|
_currentGame.value = null
|
||||||
|
_currentStream.value = null
|
||||||
|
_allStreams.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentGame,
|
||||||
|
currentStream,
|
||||||
|
allStreams,
|
||||||
|
setCurrentGame,
|
||||||
|
clearGameData
|
||||||
|
}
|
||||||
|
})
|
||||||
579
src/views/Admin.vue
Normal file
579
src/views/Admin.vue
Normal file
@@ -0,0 +1,579 @@
|
|||||||
|
<template>
|
||||||
|
<div class="games-container">
|
||||||
|
<h2>NBA赛事列表</h2>
|
||||||
|
|
||||||
|
<div class="game-list">
|
||||||
|
<div v-for="game in gamesData" :key="game.id" class="game-item">
|
||||||
|
<div class="game-info">
|
||||||
|
<span class="game-date">{{ formatDate(game.date) }}</span>
|
||||||
|
<span class="game-id">ID: {{ game.gameId }}</span>
|
||||||
|
|
||||||
|
<div class="teams">
|
||||||
|
<div class="team">
|
||||||
|
<img
|
||||||
|
:src="game.homeTeamLogoDark"
|
||||||
|
:alt="game.homeTeamName"
|
||||||
|
class="team-logo"
|
||||||
|
/>
|
||||||
|
<span class="team-name">{{ game.homeTeamName }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="vs">VS</span>
|
||||||
|
<div class="team">
|
||||||
|
<img
|
||||||
|
:src="game.awayTeamLogoDark"
|
||||||
|
:alt="game.awayTeamName"
|
||||||
|
class="team-logo"
|
||||||
|
/>
|
||||||
|
<span class="team-name">{{ game.awayTeamName }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="start-time">{{ formatTime(game.startTime) }}</span>
|
||||||
|
|
||||||
|
<!-- 显示直播URL -->
|
||||||
|
<div
|
||||||
|
v-if="getGameUrls(game.gameId).length > 0"
|
||||||
|
class="urls-container"
|
||||||
|
>
|
||||||
|
<h4>直播链接:</h4>
|
||||||
|
<div
|
||||||
|
v-for="(url, index) in getGameUrls(game.gameId)"
|
||||||
|
:key="index"
|
||||||
|
class="url-item"
|
||||||
|
>
|
||||||
|
<span class="url-type">{{ formatUrlType(url.type) }}:</span>
|
||||||
|
<a :href="url.url" target="_blank" class="url-link">{{
|
||||||
|
url.url
|
||||||
|
}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="no-urls">暂无直播链接</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="add-url-btn" @click="openAddUrlDialog(game)">
|
||||||
|
添加直播
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加直播URL的对话框 -->
|
||||||
|
<!-- 添加直播URL的对话框 -->
|
||||||
|
<div v-if="showDialog" class="dialog-overlay">
|
||||||
|
<div class="dialog-content">
|
||||||
|
<h3>
|
||||||
|
为 {{ selectedGame.homeTeamName }} VS
|
||||||
|
{{ selectedGame.awayTeamName }} 添加直播URL
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="url-inputs">
|
||||||
|
<div v-for="(url, index) in newUrls" :key="index" class="url-item">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>直播类型 {{ index + 1 }}:</label>
|
||||||
|
<select v-model="url.type" class="url-type-select">
|
||||||
|
<option value="tx">腾讯体育</option>
|
||||||
|
<option value="mg">咪咕视频</option>
|
||||||
|
<option value="wl">纬来体育</option>
|
||||||
|
<option value="nba">NBA原声</option>
|
||||||
|
<option value="zb">其他平台</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>直播地址 {{ index + 1 }}:</label>
|
||||||
|
<input
|
||||||
|
v-model="url.url"
|
||||||
|
type="text"
|
||||||
|
placeholder="请输入完整的直播URL"
|
||||||
|
class="url-input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-if="newUrls.length > 1"
|
||||||
|
class="remove-btn"
|
||||||
|
@click="removeUrl(index)"
|
||||||
|
>
|
||||||
|
<i class="el-icon-remove"></i> 删除
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="add-more-btn" @click="addMoreUrl">
|
||||||
|
<i class="el-icon-circle-plus"></i> 添加更多直播源
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<button class="cancel-btn" @click="closeDialog">取消</button>
|
||||||
|
<button
|
||||||
|
class="confirm-btn"
|
||||||
|
@click="submitUrls"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
>
|
||||||
|
<span v-if="!isSubmitting">确认添加</span>
|
||||||
|
<span v-else>正在提交...</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onBeforeMount } from "vue";
|
||||||
|
import { ElMessage, ElLoading } from "element-plus";
|
||||||
|
import { games, urls, addUrls as apiAddUrls,go } from "@/api/nba";
|
||||||
|
|
||||||
|
// 组件状态
|
||||||
|
const gamesData = ref([]);
|
||||||
|
const urlData = ref([]);
|
||||||
|
const showDialog = ref(false);
|
||||||
|
const selectedGame = ref(null);
|
||||||
|
const newUrls = ref([{ type: "tx", url: "" }]);
|
||||||
|
const isSubmitting = ref(false);
|
||||||
|
|
||||||
|
// 获取比赛和URL数据
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const [urlsRes, gamesRes] = await Promise.all([urls(), games()]);
|
||||||
|
urlData.value = urlsRes || [];
|
||||||
|
gamesData.value = gamesRes || [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error("获取数据失败:", err);
|
||||||
|
ElMessage.error("获取比赛数据失败,请刷新重试");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 提交直播链接
|
||||||
|
const submitUrls = async () => {
|
||||||
|
// 验证URL格式
|
||||||
|
const validUrls = newUrls.value
|
||||||
|
.filter((item) => item.url.trim() !== "")
|
||||||
|
.map((item) => ({
|
||||||
|
type: item.type,
|
||||||
|
url: item.url.trim(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (validUrls.length === 0) {
|
||||||
|
ElMessage.warning("请至少输入一个有效的直播URL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证URL格式是否正确
|
||||||
|
for (const url of validUrls) {
|
||||||
|
if (!isValidUrl(url.url)) {
|
||||||
|
ElMessage.warning(`直播地址格式不正确: ${url.url}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isSubmitting.value = true;
|
||||||
|
const loading = ElLoading.service({
|
||||||
|
lock: true,
|
||||||
|
text: "正在提交直播链接...",
|
||||||
|
background: "rgba(0, 0, 0, 0.7)",
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用API添加URL
|
||||||
|
await apiAddUrls(selectedGame.value.gameId, validUrls);
|
||||||
|
|
||||||
|
// 更新本地数据
|
||||||
|
updateLocalUrls(selectedGame.value.gameId, validUrls);
|
||||||
|
|
||||||
|
ElMessage.success("直播链接添加成功!");
|
||||||
|
closeDialog();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("添加直播URL失败:", error);
|
||||||
|
ElMessage.error(`添加失败: ${error.message || "服务器错误"}`);
|
||||||
|
} finally {
|
||||||
|
loading.close();
|
||||||
|
isSubmitting.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新本地URL数据
|
||||||
|
const updateLocalUrls = (gameId, urlsToAdd) => {
|
||||||
|
const existingIndex = urlData.value.findIndex(
|
||||||
|
(item) => item.gameId === gameId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingIndex >= 0) {
|
||||||
|
// 合并现有URL
|
||||||
|
urlData.value[existingIndex].urls = [
|
||||||
|
...urlData.value[existingIndex].urls,
|
||||||
|
...urlsToAdd,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
// 添加新比赛URL
|
||||||
|
urlData.value.push({
|
||||||
|
gameId,
|
||||||
|
urls: urlsToAdd,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// URL验证函数
|
||||||
|
const isValidUrl = (url) => {
|
||||||
|
try {
|
||||||
|
new URL(url);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeMount(async () => {
|
||||||
|
const storedPassword = localStorage.getItem("password");
|
||||||
|
if (storedPassword && storedPassword == "inspur123") {
|
||||||
|
console.log("=======进入后台======");
|
||||||
|
// return;
|
||||||
|
} else {
|
||||||
|
const password = prompt("请输入密码:");
|
||||||
|
try {
|
||||||
|
const response = await go(password);
|
||||||
|
if (response == true) {
|
||||||
|
// console.log("=======进入后台======");
|
||||||
|
localStorage.setItem("password", password);
|
||||||
|
} else {
|
||||||
|
// console.log("==========密码错误=========");
|
||||||
|
window.location.href = "/";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// console.error("验证密码失败:", err);
|
||||||
|
window.location.href = "/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const [res_urls, response] = await Promise.all([urls(), games()]);
|
||||||
|
urlData.value = res_urls || [];
|
||||||
|
gamesData.value = response || [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error("获取数据失败:", err);
|
||||||
|
gamesData.value = [];
|
||||||
|
urlData.value = [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 根据gameId获取对应的URLs
|
||||||
|
const getGameUrls = (gameId) => {
|
||||||
|
const gameUrls = urlData.value.find((item) => item[gameId]);
|
||||||
|
return gameUrls ? gameUrls[gameId] : [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化URL类型显示
|
||||||
|
const formatUrlType = (type) => {
|
||||||
|
const typeMap = {
|
||||||
|
tx: "腾讯",
|
||||||
|
wl: "纬来",
|
||||||
|
mg: "咪咕",
|
||||||
|
nba: "原声",
|
||||||
|
zb: "其他",
|
||||||
|
qq: "腾讯",
|
||||||
|
wx: "微信",
|
||||||
|
};
|
||||||
|
return typeMap[type] || type;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openAddUrlDialog = (game) => {
|
||||||
|
selectedGame.value = game;
|
||||||
|
newUrls.value = [{ type: "tx", url: "" }];
|
||||||
|
showDialog.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDialog = () => {
|
||||||
|
showDialog.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addMoreUrl = () => {
|
||||||
|
newUrls.value.push({ type: "tx", url: "" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeUrl = (index) => {
|
||||||
|
newUrls.value.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addUrls = async () => {
|
||||||
|
const validUrls = newUrls.value.filter((item) => item.url.trim() !== "");
|
||||||
|
|
||||||
|
if (validUrls.length === 0) {
|
||||||
|
alert("请至少输入一个有效的直播URL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
gameId: selectedGame.value.gameId,
|
||||||
|
urls: validUrls,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// console.log("提交的数据:", payload);
|
||||||
|
alert("直播URL添加成功!");
|
||||||
|
closeDialog();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("添加直播URL失败:", error);
|
||||||
|
alert("添加失败,请重试");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDate = (dateString) => {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return `${date.getMonth() + 1}月${date.getDate()}日`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTime = (timeString) => {
|
||||||
|
const time = new Date(timeString);
|
||||||
|
return `${time.getHours()}:${time.getMinutes().toString().padStart(2, "0")}`;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 添加一些样式使URL显示更美观 */
|
||||||
|
.urls-container {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.url-item {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.url-type {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 8px;
|
||||||
|
min-width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.url-link {
|
||||||
|
color: #0066cc;
|
||||||
|
text-decoration: none;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.url-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-urls {
|
||||||
|
margin-top: 10px;
|
||||||
|
color: #999;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.games-container {
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-date,
|
||||||
|
.game-id,
|
||||||
|
.start-time {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.teams {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-logo {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-name {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vs {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #e63946;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-url-btn {
|
||||||
|
padding: 8px 15px;
|
||||||
|
background-color: #4caf50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #45a049;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 对话框样式 */
|
||||||
|
.dialog-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content {
|
||||||
|
background-color: white;
|
||||||
|
padding: 25px;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 500px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.url-inputs {
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.url-item {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input,
|
||||||
|
.form-group select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
background-color: #ff4444;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #cc0000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-more-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border: 1px dashed #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn,
|
||||||
|
.confirm-btn {
|
||||||
|
padding: 8px 15px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #333;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-btn {
|
||||||
|
background-color: #2196f3;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #0b7dda;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,26 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- <LiveStream
|
<LiveStream @goBack="goBack" />
|
||||||
:liveUrl="liveUrl"
|
</template>
|
||||||
:streamType="streamType"
|
|
||||||
:gameInfo="gameInfo"
|
<script setup>
|
||||||
@goBack="goBack"
|
import { useRouter } from 'vue-router'
|
||||||
/> -->
|
import LiveStream from '@/components/LiveStream.vue'
|
||||||
<LiveStream />
|
|
||||||
</template>
|
const router = useRouter()
|
||||||
|
|
||||||
<script setup>
|
const goBack = () => {
|
||||||
import LiveStream from '@/components/LiveStream.vue';
|
router.go(-1)
|
||||||
// import { ref } from 'vue';
|
}
|
||||||
// import { useRoute, useRouter } from 'vue-router'
|
</script>
|
||||||
|
|
||||||
// const route = useRoute(); // 注意这里改成了 route 而不是 useRoute
|
<style lang="scss" scoped>
|
||||||
// const liveUrl = ref('');
|
/* 可以添加一些页面样式 */
|
||||||
// liveUrl.value = route.query.url;
|
</style>
|
||||||
// console.log('play页面接收的直播地址:', liveUrl.value);
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
161
src/views/Test.vue
Normal file
161
src/views/Test.vue
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
<template>
|
||||||
|
<div class="player-container">
|
||||||
|
<!-- 播放器容器 -->
|
||||||
|
<div id="dplayer" ref="dplayerRef"></div>
|
||||||
|
|
||||||
|
<!-- 浏览器不支持提示 -->
|
||||||
|
<div v-if="!isSupported" class="unsupported-tip">
|
||||||
|
当前浏览器不支持HLS直播流播放,请使用Chrome/Firefox/Edge等现代浏览器
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 移动端自动播放提示 -->
|
||||||
|
<div v-if="showPlayButton" class="play-button" @click="handleClickPlay">
|
||||||
|
<span class="icon">▶</span>
|
||||||
|
<span>点击播放</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||||
|
import DPlayer from 'dplayer'
|
||||||
|
import Hls from 'hls.js'
|
||||||
|
// import 'dplayer/dist/DPlayer.min.css'
|
||||||
|
|
||||||
|
const dplayerRef = ref(null)
|
||||||
|
const dp = ref(null)
|
||||||
|
const isSupported = ref(true)
|
||||||
|
const showPlayButton = ref(false)
|
||||||
|
|
||||||
|
// 初始化播放器
|
||||||
|
const initPlayer = () => {
|
||||||
|
// 销毁旧实例
|
||||||
|
if (dp.value) {
|
||||||
|
dp.value.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
container: dplayerRef.value,
|
||||||
|
live: true,
|
||||||
|
autoplay: !isMobile(), // 非移动端自动播放
|
||||||
|
video: {
|
||||||
|
url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', // 替换为实际m3u8地址
|
||||||
|
type: 'customHls',
|
||||||
|
customType: {
|
||||||
|
customHls: (video, player) => {
|
||||||
|
const hls = new Hls({
|
||||||
|
enableWorker: true, // 启用HLS.js的Web Worker
|
||||||
|
maxBufferLength: 30, // 最大缓冲长度(秒)
|
||||||
|
maxMaxBufferLength: 600, // 最大缓冲限制
|
||||||
|
maxBufferSize: 60 * 1000 * 1000, // 最大缓冲大小(bytes)
|
||||||
|
maxBufferHole: 0.5 // 最大允许的缓冲缺口(秒)
|
||||||
|
})
|
||||||
|
|
||||||
|
hls.loadSource(video.src)
|
||||||
|
hls.attachMedia(video)
|
||||||
|
|
||||||
|
// 错误处理
|
||||||
|
hls.on(Hls.Events.ERROR, (event, data) => {
|
||||||
|
if (data.fatal) {
|
||||||
|
switch (data.type) {
|
||||||
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
||||||
|
console.error('网络错误,尝试重新加载')
|
||||||
|
hls.startLoad()
|
||||||
|
break
|
||||||
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
||||||
|
console.error('媒体错误,尝试恢复')
|
||||||
|
hls.recoverMediaError()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
initPlayer() // 其他错误重新初始化
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
player.on('destroy', () => hls.destroy())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是Safari且支持原生HLS
|
||||||
|
if (isSafari() && video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||||
|
options.video.type = 'hls'
|
||||||
|
delete options.video.customType
|
||||||
|
}
|
||||||
|
|
||||||
|
dp.value = new DPlayer(options)
|
||||||
|
|
||||||
|
// 移动端需要用户交互后才能播放
|
||||||
|
if (isMobile()) {
|
||||||
|
showPlayButton.value = true
|
||||||
|
dp.value.pause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测移动端
|
||||||
|
const isMobile = () => {
|
||||||
|
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测Safari浏览器
|
||||||
|
const isSafari = () => {
|
||||||
|
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击播放按钮
|
||||||
|
const handleClickPlay = () => {
|
||||||
|
dp.value.play()
|
||||||
|
showPlayButton.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 检查浏览器支持情况
|
||||||
|
if (!Hls.isSupported() && !video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||||
|
isSupported.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
initPlayer()
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (dp.value) {
|
||||||
|
dp.value.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.player-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: #000;
|
||||||
|
|
||||||
|
#dplayer {
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
padding-bottom: 56.25%; /* 16:9 比例 */
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
/* 确保 DPlayer 内部视频元素正确填充 */
|
||||||
|
& > div {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 强制视频填充整个播放器 */
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain; /* 或使用 'cover' 填充整个容器(可能裁剪边缘) */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user