commit
This commit is contained in:
@@ -4,11 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
<title>NBA在线</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<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>
|
||||
</html>
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
"element-plus": "^2.9.7",
|
||||
"flv.js": "^1.6.2",
|
||||
"hls.js": "^1.6.2",
|
||||
"pinia": "^3.0.2",
|
||||
"router": "^2.2.0",
|
||||
"video.js": "^8.22.0",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "4"
|
||||
},
|
||||
|
||||
201
pnpm-lock.yaml
generated
201
pnpm-lock.yaml
generated
@@ -26,9 +26,15 @@ importers:
|
||||
hls.js:
|
||||
specifier: ^1.6.2
|
||||
version: 1.6.2
|
||||
pinia:
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2(vue@3.5.13)
|
||||
router:
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0
|
||||
video.js:
|
||||
specifier: ^8.22.0
|
||||
version: 8.22.0
|
||||
vue:
|
||||
specifier: ^3.5.13
|
||||
version: 3.5.13
|
||||
@@ -182,6 +188,10 @@ packages:
|
||||
peerDependencies:
|
||||
'@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':
|
||||
resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -528,6 +538,19 @@ packages:
|
||||
'@types/web-bluetooth@0.0.16':
|
||||
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':
|
||||
resolution: {integrity: sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
@@ -566,6 +589,9 @@ packages:
|
||||
'@vue/devtools-api@6.6.4':
|
||||
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
|
||||
|
||||
'@vue/devtools-api@7.7.5':
|
||||
resolution: {integrity: sha512-HYV3tJGARROq5nlVMJh5KKHk7GU8Au3IrrmNNqr978m0edxgpHgYPDoNUGrvEgIbObz09SQezFR3A1EVmB5WZg==}
|
||||
|
||||
'@vue/devtools-core@7.7.5':
|
||||
resolution: {integrity: sha512-ElKr0NDor57gVaT+gMQ8kcVP4uFGqHcxuuQndW/rPwh6aHWvEcUL3sxL8cEk+e1Rdt28kS88erpsiIMO6hEENQ==}
|
||||
peerDependencies:
|
||||
@@ -603,6 +629,13 @@ packages:
|
||||
'@vueuse/shared@9.13.0':
|
||||
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:
|
||||
resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
|
||||
|
||||
@@ -693,6 +726,9 @@ packages:
|
||||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
dom-walk@0.1.2:
|
||||
resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==}
|
||||
|
||||
dplayer@1.27.1:
|
||||
resolution: {integrity: sha512-2laBMXs5V1B9zPwJ7eAIw/OBo+Xjvy03i4GHTk3Cg+IWbrq8rKMFO0fFr6ClAYotYOCcFGOvaJDkOZcgKllsCA==}
|
||||
|
||||
@@ -809,6 +845,9 @@ packages:
|
||||
resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
global@4.4.0:
|
||||
resolution: {integrity: sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==}
|
||||
|
||||
globals@11.12.0:
|
||||
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -854,6 +893,9 @@ packages:
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
hasBin: true
|
||||
|
||||
is-function@1.0.2:
|
||||
resolution: {integrity: sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==}
|
||||
|
||||
is-inside-container@1.0.0:
|
||||
resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
|
||||
engines: {node: '>=14.16'}
|
||||
@@ -920,6 +962,9 @@ packages:
|
||||
lru-cache@5.1.1:
|
||||
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:
|
||||
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
||||
|
||||
@@ -938,9 +983,16 @@ packages:
|
||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
min-document@2.19.0:
|
||||
resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==}
|
||||
|
||||
mitt@3.0.1:
|
||||
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:
|
||||
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -948,6 +1000,11 @@ packages:
|
||||
ms@2.1.3:
|
||||
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:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
@@ -1005,6 +1062,19 @@ packages:
|
||||
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
|
||||
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:
|
||||
resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
@@ -1013,12 +1083,19 @@ packages:
|
||||
resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==}
|
||||
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:
|
||||
resolution: {integrity: sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==}
|
||||
|
||||
proxy-from-env@1.1.0:
|
||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||
|
||||
regenerator-runtime@0.14.1:
|
||||
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
|
||||
|
||||
rfdc@1.4.1:
|
||||
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
|
||||
|
||||
@@ -1239,6 +1316,21 @@ packages:
|
||||
varint@6.0.0:
|
||||
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:
|
||||
resolution: {integrity: sha512-W9LOGAyGMrbGArYJN4LBCdOC5+Zwh7dHvOHC0KmGKkJhsOzaKbpo/jEjpPKVHIW0/jBWj8RZG0NUxfgA8BxgAg==}
|
||||
peerDependencies:
|
||||
@@ -1519,6 +1611,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/runtime@7.27.0':
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.1
|
||||
|
||||
'@babel/template@7.27.0':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.26.2
|
||||
@@ -1739,6 +1835,28 @@ snapshots:
|
||||
|
||||
'@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)':
|
||||
dependencies:
|
||||
vite: 6.3.0(sass-embedded@1.86.3)
|
||||
@@ -1805,6 +1923,10 @@ snapshots:
|
||||
|
||||
'@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)':
|
||||
dependencies:
|
||||
'@vue/devtools-kit': 7.7.5
|
||||
@@ -1874,6 +1996,15 @@ snapshots:
|
||||
- '@vue/composition-api'
|
||||
- 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: {}
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
@@ -1957,6 +2088,8 @@ snapshots:
|
||||
|
||||
depd@2.0.0: {}
|
||||
|
||||
dom-walk@0.1.2: {}
|
||||
|
||||
dplayer@1.27.1:
|
||||
dependencies:
|
||||
axios: 1.2.3
|
||||
@@ -2122,6 +2255,11 @@ snapshots:
|
||||
'@sec-ant/readable-stream': 0.4.1
|
||||
is-stream: 4.0.1
|
||||
|
||||
global@4.4.0:
|
||||
dependencies:
|
||||
min-document: 2.19.0
|
||||
process: 0.11.10
|
||||
|
||||
globals@11.12.0: {}
|
||||
|
||||
gopd@1.2.0: {}
|
||||
@@ -2150,6 +2288,8 @@ snapshots:
|
||||
|
||||
is-docker@3.0.0: {}
|
||||
|
||||
is-function@1.0.2: {}
|
||||
|
||||
is-inside-container@1.0.0:
|
||||
dependencies:
|
||||
is-docker: 3.0.0
|
||||
@@ -2198,6 +2338,12 @@ snapshots:
|
||||
dependencies:
|
||||
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:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
@@ -2212,12 +2358,28 @@ snapshots:
|
||||
dependencies:
|
||||
mime-db: 1.52.0
|
||||
|
||||
min-document@2.19.0:
|
||||
dependencies:
|
||||
dom-walk: 0.1.2
|
||||
|
||||
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: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
mux.js@7.1.0:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.0
|
||||
global: 4.4.0
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
nanoid@5.1.5: {}
|
||||
@@ -2256,6 +2418,15 @@ snapshots:
|
||||
|
||||
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:
|
||||
dependencies:
|
||||
nanoid: 3.3.11
|
||||
@@ -2266,10 +2437,14 @@ snapshots:
|
||||
dependencies:
|
||||
parse-ms: 4.0.0
|
||||
|
||||
process@0.11.10: {}
|
||||
|
||||
promise-polyfill@8.3.0: {}
|
||||
|
||||
proxy-from-env@1.1.0: {}
|
||||
|
||||
regenerator-runtime@0.14.1: {}
|
||||
|
||||
rfdc@1.4.1: {}
|
||||
|
||||
rollup@4.40.0:
|
||||
@@ -2463,6 +2638,32 @@ snapshots:
|
||||
|
||||
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)):
|
||||
dependencies:
|
||||
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 axios from 'axios'
|
||||
|
||||
const nbaapi = axios.create({
|
||||
baseURL: 'http://localhost:9005/api',
|
||||
baseURL: 'http://api.new9.me/api',
|
||||
// baseURL: 'http://110.42.255.182:8080',
|
||||
timeout: 2000,
|
||||
})
|
||||
@@ -12,29 +13,72 @@ const urls = async () => {
|
||||
method: 'get',
|
||||
})
|
||||
.then((response) => {
|
||||
// console.log(response.data); // 可选:调试用
|
||||
return response.data; // 返回数据
|
||||
return response.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('获取直播URL失败:', error);
|
||||
throw error; // 可以选择抛出错误或返回默认值,比如 return []
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
const games = () => {
|
||||
nbaapi({
|
||||
const games = async () => {
|
||||
return await nbaapi({
|
||||
url: '/games',
|
||||
method: 'get',
|
||||
}).then((response) => {
|
||||
console.log(response.data);
|
||||
})
|
||||
}
|
||||
.then((response) => {
|
||||
// console.log(response.data); // 调试用
|
||||
return response.data; // 确保返回数据
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('获取赛事数据失败:', error);
|
||||
throw error; // 或者返回空数组 return []
|
||||
});
|
||||
};
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export {schedule,games,urls};
|
||||
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>
|
||||
<div class="video-container">
|
||||
<div
|
||||
v-for="(video, index) in videoList"
|
||||
:key="index"
|
||||
class="video-card"
|
||||
@click="handleCardClick(index)"
|
||||
<div class="live-stream-container">
|
||||
<!-- 比赛信息 -->
|
||||
<div class="game-header" v-if="gameData">
|
||||
<div class="team-info away-team">
|
||||
<img :src="gameData.awayTeam.logo" :alt="gameData.awayTeam.name" />
|
||||
<div class="team-details">
|
||||
<h3>{{ gameData.awayTeam.city }}</h3>
|
||||
<p>{{ gameData.awayTeam.name }}</p>
|
||||
<span>{{ gameData.awayTeam.record }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vs-circle">VS</div>
|
||||
|
||||
<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 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 }"
|
||||
>
|
||||
<div class="video-wrapper">
|
||||
<div :id="'dplayer-' + index" class="dplayer-container"></div>
|
||||
</div>
|
||||
<div class="video-info">
|
||||
<h3 class="video-title">{{ video.title }}</h3>
|
||||
<p class="video-desc">{{ video.description }}</p>
|
||||
{{ getStreamName(stream.type) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 返回按钮 -->
|
||||
<button class="back-button" @click="goBack">
|
||||
← 返回赛程
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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 Hls from 'hls.js'
|
||||
import Flv from 'flv.js'
|
||||
// 将 flv.js 注册为全局变量
|
||||
|
||||
// 注册全局变量
|
||||
window.flvjs = Flv
|
||||
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 = () => {
|
||||
videoList.value.forEach((video, index) => {
|
||||
const dp = new DPlayer({
|
||||
container: document.getElementById(`dplayer-${index}`),
|
||||
// live: true,
|
||||
screenshot: true,
|
||||
const initPlayer = () => {
|
||||
if (dpInstance.value) {
|
||||
dpInstance.value.destroy();
|
||||
}
|
||||
|
||||
dpInstance.value = new DPlayer({
|
||||
container: document.getElementById('dplayer-live'),
|
||||
live: 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,
|
||||
url: currentStream.value?.url || '',
|
||||
type: 'auto'
|
||||
}
|
||||
}
|
||||
})
|
||||
console.log(dp.plugins.flv); // flv 实例
|
||||
// 监听播放器的事件
|
||||
dpInstances.value.push(dp)
|
||||
});
|
||||
|
||||
})
|
||||
// 强制设置视频尺寸
|
||||
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 handleCardClick = (index) => {
|
||||
// 暂停所有其他播放器
|
||||
dpInstances.value.forEach((dp, i) => {
|
||||
if (i !== index && !dp.video.paused) {
|
||||
dp.pause()
|
||||
// 获取直播源名称
|
||||
const getStreamName = (type) => {
|
||||
const names = {
|
||||
tx: '企鹅体育',
|
||||
wl: '纬来体育',
|
||||
mg: '咪咕体育',
|
||||
nba: '高清原声',
|
||||
zb: '高清直播'
|
||||
}
|
||||
})
|
||||
return names[type] || type
|
||||
}
|
||||
|
||||
// 返回赛程页
|
||||
const goBack = () => {
|
||||
gameStore.clearGameData()
|
||||
router.go(-1)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initDPlayers()
|
||||
// 默认选择第一个直播源
|
||||
if (allStreams.value.length > 0 && !currentStream.value) {
|
||||
currentStream.value = allStreams.value[0]
|
||||
}
|
||||
initPlayer()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// 销毁所有播放器实例
|
||||
dpInstances.value.forEach(dp => {
|
||||
dp.destroy()
|
||||
})
|
||||
if (dpInstance.value) {
|
||||
dpInstance.value.destroy()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<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;
|
||||
flex-wrap: wrap;
|
||||
padding: 40px 0;
|
||||
justify-content: center;
|
||||
// align-items: center;
|
||||
|
||||
.video-card {
|
||||
flex: 1 1 calc(33.333% - 20px);
|
||||
min-width: 768px;
|
||||
max-width: 70%;
|
||||
background: #d6f3f0;
|
||||
align-items: center;
|
||||
gap: 40px;
|
||||
margin-bottom: 20px;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
// transform: translateY(-5px);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.video-wrapper {
|
||||
position: relative;
|
||||
padding-top: 56.25%; /* 16:9 宽高比 */
|
||||
overflow: hidden;
|
||||
.team-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
|
||||
.dplayer-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.video-info {
|
||||
padding: 15px;
|
||||
.team-details {
|
||||
text-align: center;
|
||||
|
||||
.video-title {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 1.1rem;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.video-desc {
|
||||
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;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 移动端适配 */
|
||||
.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;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: #e0e0e0;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #3498db;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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) {
|
||||
.video-container {
|
||||
.video-card {
|
||||
flex: 1 1 calc(50% - 15px);
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
.game-header {
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.video-container {
|
||||
|
||||
.video-card {
|
||||
flex: 1 1 100%;
|
||||
min-width: 100%;
|
||||
.vs-circle {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,12 +2,12 @@
|
||||
<div class="nba-schedule-container">
|
||||
<!-- 赞助商信息 -->
|
||||
<div v-if="scheduleData?.data?.sponsor" class="sponsor-banner">
|
||||
<span>所有内容均来源互联网,如有侵权联系邮箱:xdd9@vip.qq.com</span>
|
||||
<img
|
||||
<span>所有内容均来源互联网,有问题请联系邮箱:xdd9@vip.qq.com</span>
|
||||
<!-- <img
|
||||
:src="scheduleData.data.sponsor.logo"
|
||||
:alt="scheduleData.data.sponsor.name"
|
||||
class="sponsor-logo"
|
||||
/>
|
||||
/> -->
|
||||
</div>
|
||||
|
||||
<!-- 赛程日期导航 -->
|
||||
@@ -49,7 +49,10 @@
|
||||
<!-- 客队信息 -->
|
||||
<div
|
||||
class="team away-team"
|
||||
:class="{ 'tbd-team': !game.teamValid }"
|
||||
:class="{
|
||||
'tbd-team': !game.teamValid,
|
||||
winner: isWinner(game, 'away'), // 添加判断是否为胜者
|
||||
}"
|
||||
>
|
||||
<img
|
||||
:src="game.awayTeamLogoDark"
|
||||
@@ -91,7 +94,10 @@
|
||||
<!-- 主队信息 -->
|
||||
<div
|
||||
class="team home-team"
|
||||
:class="{ 'tbd-team': !game.teamValid }"
|
||||
:class="{
|
||||
'tbd-team': !game.teamValid,
|
||||
winner: isWinner(game, 'home'), // 添加判断是否为胜者
|
||||
}"
|
||||
>
|
||||
<img
|
||||
:src="game.homeTeamLogoDark"
|
||||
@@ -115,25 +121,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 直播间按钮区域(仅当天进行中的比赛显示) -->
|
||||
<div class="live-buttons" v-if="shouldShowLiveArea(game)">
|
||||
<template v-if="game.status === 2 && hasLiveStreams(game.gameId)">
|
||||
<div class="live-buttons">
|
||||
<!-- 只有当比赛未结束且是当天比赛时才显示直播区域 -->
|
||||
<template v-if="game.status !== 3 && shouldShowLiveArea(game)">
|
||||
<template v-if="hasLiveStreams(game.gameId)">
|
||||
<!-- 直播按钮 -->
|
||||
<button
|
||||
v-for="stream in getLiveStreams(game.gameId)"
|
||||
:key="stream.type"
|
||||
@click="goToLive(game, stream)"
|
||||
class="live-btn"
|
||||
:class="{
|
||||
primary: stream.type === 'qq',
|
||||
secondary: stream.type !== 'qq',
|
||||
}"
|
||||
@click="goToLive(stream.url)"
|
||||
>
|
||||
<span class="btn-icon">📺</span>
|
||||
<span>{{ getStreamName(stream.type) }}</span>
|
||||
{{ getStreamName(stream.type) }}
|
||||
</button>
|
||||
</template>
|
||||
<div v-else-if="game.status === 1" class="no-live">未开始</div>
|
||||
<div v-else class="no-live">无直播信号</div>
|
||||
</template>
|
||||
<div v-else-if="game.status === 3" class="no-live">
|
||||
比赛已结束
|
||||
</div>
|
||||
<div v-else class="no-live">未开始</div>
|
||||
</div>
|
||||
|
||||
<!-- 比赛场地和赛季信息 -->
|
||||
@@ -162,7 +170,8 @@ import { computed, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { urls } from "@/api/nba";
|
||||
import { onMounted } from "vue";
|
||||
|
||||
import { useGameStore } from "@/stores/game";
|
||||
const gameStore = useGameStore();
|
||||
const router = useRouter();
|
||||
const urlsData = ref([]);
|
||||
|
||||
@@ -170,22 +179,21 @@ const shouldShowLiveArea = (game) => {
|
||||
// 1. 已结束的比赛不显示
|
||||
if (game.status === 3) return false;
|
||||
|
||||
const gameDate = new Date(game.dateTimeUtc);
|
||||
// 2. 获取今天的日期(北京时间)
|
||||
const today = new Date();
|
||||
const todayStr = `${today.getFullYear()}-${(today.getMonth() + 1)
|
||||
.toString()
|
||||
.padStart(2, "0")}-${today.getDate().toString().padStart(2, "0")}`;
|
||||
|
||||
// 2. 只显示当天及未来的比赛
|
||||
// 清除时间部分,只比较日期
|
||||
today.setHours(0, 0, 0, 0);
|
||||
gameDate.setHours(0, 0, 0, 0);
|
||||
|
||||
return gameDate >= today;
|
||||
// 3. 直接比较 startDate(已经是北京时间)
|
||||
return game.startDate === todayStr;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const response = await urls();
|
||||
urlsData.value = response || [];
|
||||
// console.log("获取的直播URL:", urlsData.value);
|
||||
// console.log("获取的直播URL数据:", urlsData.value); // 检查数据是否正确
|
||||
} catch (err) {
|
||||
console.error("获取直播URL失败:", err);
|
||||
urlsData.value = [];
|
||||
@@ -205,25 +213,31 @@ const isLiveGame = (game) => {
|
||||
const hasLiveStreams = (gameId) => {
|
||||
if (!urlsData.value || !gameId) return false;
|
||||
|
||||
// 查找匹配的gameId
|
||||
const gameStreams = urlsData.value.find((item) => item[gameId]);
|
||||
return !!gameStreams;
|
||||
// 遍历所有直播流数据
|
||||
for (const streamGroup of urlsData.value) {
|
||||
if (streamGroup[gameId]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// 获取比赛的直播流
|
||||
const getLiveStreams = (gameId) => {
|
||||
if (!urlsData.value || !gameId) return [];
|
||||
|
||||
const gameStreams = urlsData.value.find((item) => item[gameId]);
|
||||
return gameStreams ? gameStreams[gameId] : [];
|
||||
const id = String(gameId); // 转为字符串
|
||||
for (const streamGroup of urlsData.value) {
|
||||
if (streamGroup[id]) return streamGroup[id];
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
// 获取流名称
|
||||
const getStreamName = (type) => {
|
||||
const names = {
|
||||
tx: "TX直播",
|
||||
wl: "纬来直播",
|
||||
nba: "原声直播",
|
||||
tx: "企鹅体育",
|
||||
wl: "纬来体育",
|
||||
nba: "高清原声",
|
||||
mg: "咪咕体育",
|
||||
zb: "高清直播",
|
||||
// 可以添加更多类型
|
||||
};
|
||||
@@ -231,14 +245,39 @@ const getStreamName = (type) => {
|
||||
};
|
||||
|
||||
// 跳转到直播页面
|
||||
const goToLive = (url) => {
|
||||
if (!url) return;
|
||||
const goToLive = (game, stream) => {
|
||||
// 准备比赛数据
|
||||
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({
|
||||
name: "Play",
|
||||
query: {
|
||||
url: url,
|
||||
// 其他参数...
|
||||
params: {
|
||||
gameId: game.gameId,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -324,6 +363,19 @@ const changeDate = (direction) => {
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
@@ -520,8 +572,8 @@ const changeDate = (direction) => {
|
||||
}
|
||||
|
||||
.game-not-started {
|
||||
color: #6c757d;
|
||||
font-size: 16px;
|
||||
color: #5a7cec;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.game-in-progress {
|
||||
@@ -693,4 +745,20 @@ const changeDate = (direction) => {
|
||||
border-radius: 8px;
|
||||
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>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import router from './router'
|
||||
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import { createPinia } from 'pinia'
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(router)
|
||||
|
||||
app.use(ElementPlus)
|
||||
app.use(createPinia())
|
||||
app.mount('#app')
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import IndexVue from '@/views/Index.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 = [
|
||||
{
|
||||
path: '/',
|
||||
@@ -10,10 +14,25 @@ const routes = [
|
||||
props: route => ({ query: route.query })
|
||||
},
|
||||
{
|
||||
path: '/play',
|
||||
path: '/play/:gameId',
|
||||
name: 'Play',
|
||||
component: PlayVue,
|
||||
props: route => ({ query: route.query })
|
||||
component: () => import('@/views/Play.vue'),
|
||||
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
|
||||
})
|
||||
|
||||
// 添加全局路由守卫
|
||||
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
|
||||
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>
|
||||
<!-- <LiveStream
|
||||
:liveUrl="liveUrl"
|
||||
:streamType="streamType"
|
||||
:gameInfo="gameInfo"
|
||||
@goBack="goBack"
|
||||
/> -->
|
||||
<LiveStream />
|
||||
<LiveStream @goBack="goBack" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import LiveStream from '@/components/LiveStream.vue';
|
||||
// import { ref } from 'vue';
|
||||
// import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
// const route = useRoute(); // 注意这里改成了 route 而不是 useRoute
|
||||
// const liveUrl = ref('');
|
||||
// liveUrl.value = route.query.url;
|
||||
// console.log('play页面接收的直播地址:', liveUrl.value);
|
||||
import { useRouter } from 'vue-router'
|
||||
import LiveStream from '@/components/LiveStream.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const goBack = () => {
|
||||
router.go(-1)
|
||||
}
|
||||
</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