七十二家房客-观看平台v1.2
This commit is contained in:
parent
a20c6ad819
commit
6978074af3
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
.DS_Store
|
||||
.pw-user-data
|
||||
|
||||
node_modules/
|
||||
dist/
|
||||
npm-debug.log*
|
||||
|
||||
36
README.md
36
README.md
@ -18,29 +18,35 @@
|
||||
- 前端分页加载,避免一次加载过多图片
|
||||
|
||||
---
|
||||
获取的空数据
|
||||
```json
|
||||
|
||||
{
|
||||
"updatedAt": "2026-03-10T18:00:00Z",
|
||||
"name": "七十二家房客",
|
||||
"items": []
|
||||
}
|
||||
# 运行&安装
|
||||
## 一、后端
|
||||
安装后端依赖
|
||||
```shell
|
||||
npm i
|
||||
```
|
||||
|
||||
# 安装
|
||||
开启后端服务
|
||||
```shell
|
||||
node server.js
|
||||
```
|
||||
|
||||
如需更新数据:
|
||||
```shell
|
||||
node capture.js
|
||||
```
|
||||
|
||||
## 二、前端
|
||||
## 1 安装前端依赖
|
||||
|
||||
```
|
||||
npm install
|
||||
npm i
|
||||
```
|
||||
|
||||
运行前端服务:
|
||||
```shell
|
||||
npm run dev
|
||||
```
|
||||
|
||||
安装播放器依赖:
|
||||
|
||||
```
|
||||
npm install axios hls.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"year": 2024,
|
||||
"updatedAt": "2026-03-10T20:58:50.560Z",
|
||||
"updatedAt": "2026-03-10T21:35:04.482Z",
|
||||
"items": [
|
||||
{
|
||||
"id": "7f012566d7827b5046de9f92a4d7e159",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"year": 2025,
|
||||
"updatedAt": "2026-03-10T21:02:47.178Z",
|
||||
"updatedAt": "2026-03-10T21:34:22.356Z",
|
||||
"items": [
|
||||
{
|
||||
"id": "b2b4f80b846360647d13e297c029be8d",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"year": 2026,
|
||||
"updatedAt": "2026-03-10T21:02:47.172Z",
|
||||
"updatedAt": "2026-03-10T21:33:31.594Z",
|
||||
"items": [
|
||||
{
|
||||
"id": "5abbc88ea530b5db6438a4e584e80281",
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
{
|
||||
"running": true,
|
||||
"lastMessage": "已采集第 1 页",
|
||||
"updatedAt": "2026-03-10T21:11:58.820Z",
|
||||
"currentPage": "1",
|
||||
"pageItems": 0,
|
||||
"addedCount": 0
|
||||
"running": false,
|
||||
"lastMessage": "自动采集完成",
|
||||
"updatedAt": "2026-03-10T21:35:26.989Z"
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
express cors axios
|
||||
@ -13,25 +13,79 @@ app.use(express.json())
|
||||
|
||||
ensureDataFiles()
|
||||
|
||||
app.get('/api/qishier/all', (_req, res) => {
|
||||
try {
|
||||
const cache = readAllYearsData()
|
||||
let memoryCache = {
|
||||
updatedAt: null,
|
||||
name: '七十二家房客',
|
||||
coverUrl: '',
|
||||
displayType: 0,
|
||||
years: [],
|
||||
items: []
|
||||
}
|
||||
|
||||
res.json({
|
||||
function toSimpleItem(item) {
|
||||
return {
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
coverUrl: item.coverUrl,
|
||||
releasedAt: item.releasedAt,
|
||||
timeLength: item.timeLength,
|
||||
videoUrl: item.videoUrl
|
||||
}
|
||||
}
|
||||
|
||||
function refreshMemoryCache() {
|
||||
const cache = readAllYearsData()
|
||||
memoryCache = {
|
||||
updatedAt: cache.updatedAt,
|
||||
name: cache.name || '七十二家房客',
|
||||
coverUrl: cache.coverUrl || '',
|
||||
displayType: cache.displayType || 0,
|
||||
total: cache.items?.length || 0,
|
||||
years: cache.years || [],
|
||||
list: cache.items || []
|
||||
})
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
message: '读取缓存失败',
|
||||
error: error.message
|
||||
})
|
||||
items: (cache.items || []).map(toSimpleItem)
|
||||
}
|
||||
|
||||
console.log(
|
||||
'[server] 内存缓存已刷新:',
|
||||
'total=', memoryCache.items.length,
|
||||
'years=', memoryCache.years.length
|
||||
)
|
||||
}
|
||||
|
||||
refreshMemoryCache()
|
||||
|
||||
app.get('/api/qishier/all', (_req, res) => {
|
||||
res.json({
|
||||
updatedAt: memoryCache.updatedAt,
|
||||
name: memoryCache.name,
|
||||
coverUrl: memoryCache.coverUrl,
|
||||
displayType: memoryCache.displayType,
|
||||
total: memoryCache.items.length,
|
||||
years: memoryCache.years,
|
||||
list: memoryCache.items
|
||||
})
|
||||
})
|
||||
|
||||
app.get('/api/qishier/list', (req, res) => {
|
||||
const page = Math.max(1, Number(req.query.page || 1))
|
||||
const pageSize = Math.max(1, Math.min(100, Number(req.query.pageSize || 50)))
|
||||
|
||||
const total = memoryCache.items.length
|
||||
const start = (page - 1) * pageSize
|
||||
const end = start + pageSize
|
||||
const list = memoryCache.items.slice(start, end)
|
||||
|
||||
res.json({
|
||||
updatedAt: memoryCache.updatedAt,
|
||||
name: memoryCache.name,
|
||||
coverUrl: memoryCache.coverUrl,
|
||||
displayType: memoryCache.displayType,
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
hasMore: end < total,
|
||||
years: memoryCache.years,
|
||||
list
|
||||
})
|
||||
})
|
||||
|
||||
app.get('/api/qishier/year/:year', (req, res) => {
|
||||
@ -46,7 +100,7 @@ app.get('/api/qishier/year/:year', (req, res) => {
|
||||
displayType: 0,
|
||||
total: data.items?.length || 0,
|
||||
years: [year],
|
||||
list: data.items || []
|
||||
list: (data.items || []).map(toSimpleItem)
|
||||
})
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
@ -56,6 +110,22 @@ app.get('/api/qishier/year/:year', (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/api/qishier/refresh', (_req, res) => {
|
||||
try {
|
||||
refreshMemoryCache()
|
||||
res.json({
|
||||
message: '缓存已刷新',
|
||||
total: memoryCache.items.length,
|
||||
updatedAt: memoryCache.updatedAt
|
||||
})
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
message: '刷新缓存失败',
|
||||
error: error.message
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
app.get('/api/qishier/status', (_req, res) => {
|
||||
try {
|
||||
const status = readStatus()
|
||||
@ -68,6 +138,6 @@ app.get('/api/qishier/status', (_req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
app.listen(3000, () => {
|
||||
console.log('Node 服务已启动: http://localhost:3000')
|
||||
app.listen(23822, () => {
|
||||
console.log('Node 服务已启动: http://127.0.0.1:23822')
|
||||
})
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
"cors": "^2.8.6",
|
||||
"express": "^5.2.1",
|
||||
"hls.js": "^1.6.15",
|
||||
"plyr": "^3.8.4",
|
||||
"vue": "^3.5.29"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -7,28 +7,33 @@ export interface EpisodeData {
|
||||
releasedAt: number
|
||||
timeLength: number
|
||||
videoUrl: string
|
||||
raw?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface QishierCacheResponse {
|
||||
export interface QishierListResponse {
|
||||
updatedAt: string | null
|
||||
name: string
|
||||
coverUrl: string
|
||||
displayType: number
|
||||
total: number
|
||||
beginScoreMap: Record<string, number>
|
||||
pages: Record<string, { currentPage: number; beginScore: number; count: number; capturedAt: string }>
|
||||
years: number[]
|
||||
page: number
|
||||
pageSize: number
|
||||
hasMore: boolean
|
||||
list: EpisodeData[]
|
||||
}
|
||||
|
||||
export function getAllQishierList() {
|
||||
return axios.get<QishierCacheResponse>('/api/qishier/all', {
|
||||
export function getQishierList(page = 1, pageSize = 50) {
|
||||
return axios.get<QishierListResponse>('/api/qishier/list', {
|
||||
params: {
|
||||
page,
|
||||
pageSize
|
||||
},
|
||||
timeout: 15000
|
||||
})
|
||||
}
|
||||
|
||||
export function getQishierStatus() {
|
||||
return axios.get('/api/qishier/status', {
|
||||
export function refreshQishierCache() {
|
||||
return axios.post('/api/qishier/refresh', {}, {
|
||||
timeout: 15000
|
||||
})
|
||||
}
|
||||
|
||||
21
src/types/plyr.d.ts
vendored
Normal file
21
src/types/plyr.d.ts
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
declare module 'plyr' {
|
||||
export interface PlyrOptions {
|
||||
controls?: any[]
|
||||
settings?: any[]
|
||||
speed?: {
|
||||
selected?: number
|
||||
options?: number[]
|
||||
}
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export default class Plyr {
|
||||
constructor(target: string | HTMLElement, options?: PlyrOptions)
|
||||
destroy(): void
|
||||
play(): Promise<void>
|
||||
pause(): void
|
||||
on(event: string, callback: (...args: any[]) => void): void
|
||||
off(event: string, callback: (...args: any[]) => void): void
|
||||
source: any
|
||||
}
|
||||
}
|
||||
@ -3,24 +3,21 @@
|
||||
<div class="header">
|
||||
<div>
|
||||
<h1>{{ columnInfo.name || '七十二家房客播放器' }}</h1>
|
||||
<p>共 {{ allEpisodeList.length }} 条,当前显示 {{ visibleEpisodeList.length }} 条</p>
|
||||
<p>共 {{ total }} 条,当前显示 {{ episodeList.length }} 条</p>
|
||||
<p class="sub">最后更新:{{ updatedAtText }}</p>
|
||||
</div>
|
||||
|
||||
<div class="header-actions">
|
||||
<button class="action-btn" @click="fetchList">刷新数据</button>
|
||||
<button class="action-btn" @click="handleRefresh">刷新数据</button>
|
||||
<button class="action-btn source-btn" @click="openSource">
|
||||
数据来源:荔枝网
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layout">
|
||||
<div class="player-panel">
|
||||
<video
|
||||
ref="videoRef"
|
||||
class="video"
|
||||
controls
|
||||
playsinline
|
||||
webkit-playsinline
|
||||
></video>
|
||||
<video ref="videoRef" class="video" playsinline webkit-playsinline></video>
|
||||
|
||||
<div v-if="currentEpisode" class="info">
|
||||
<h2>{{ currentEpisode.title }}</h2>
|
||||
@ -37,7 +34,7 @@
|
||||
|
||||
<div class="list-panel">
|
||||
<div
|
||||
v-for="item in visibleEpisodeList"
|
||||
v-for="item in episodeList"
|
||||
:key="item.id"
|
||||
class="episode-item"
|
||||
:class="{ active: currentEpisode?.id === item.id }"
|
||||
@ -50,9 +47,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="load-more-wrap" v-if="visibleEpisodeList.length < allEpisodeList.length">
|
||||
<button class="load-more-btn" @click="loadMoreEpisodes">
|
||||
加载更多
|
||||
<div class="load-more-wrap" v-if="hasMore">
|
||||
<button class="load-more-btn" :disabled="loadingMore" @click="loadMoreEpisodes">
|
||||
{{ loadingMore ? '加载中...' : '加载更多' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -63,30 +60,41 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
||||
import Hls from 'hls.js'
|
||||
import { getAllQishierList, type EpisodeData } from '@/api/qishier'
|
||||
import Plyr from 'plyr'
|
||||
import 'plyr/dist/plyr.css'
|
||||
import { getQishierList, refreshQishierCache, type EpisodeData } from '@/api/qishier'
|
||||
|
||||
const videoRef = ref<HTMLVideoElement | null>(null)
|
||||
const hlsRef = ref<Hls | null>(null)
|
||||
|
||||
const columnInfo = reactive({
|
||||
name: '',
|
||||
coverUrl: ''
|
||||
})
|
||||
|
||||
const allEpisodeList = ref<EpisodeData[]>([])
|
||||
const visibleEpisodeList = ref<EpisodeData[]>([])
|
||||
const episodeList = ref<EpisodeData[]>([])
|
||||
const currentEpisode = ref<EpisodeData | null>(null)
|
||||
const errorMsg = ref('')
|
||||
const updatedAt = ref<string | null>(null)
|
||||
|
||||
const pageSize = 20
|
||||
const currentVisibleCount = ref(pageSize)
|
||||
const total = ref(0)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const hasMore = ref(false)
|
||||
const loading = ref(false)
|
||||
const loadingMore = ref(false)
|
||||
|
||||
let hlsInstance: Hls | null = null
|
||||
let plyrInstance: Plyr | null = null
|
||||
|
||||
const updatedAtText = computed(() => {
|
||||
if (!updatedAt.value) return '暂无缓存'
|
||||
return new Date(updatedAt.value).toLocaleString('zh-CN')
|
||||
})
|
||||
|
||||
function openSource() {
|
||||
window.open('https://www1.gdtv.cn/', '_blank')
|
||||
}
|
||||
|
||||
function setCookie(name: string, value: string, days = 30) {
|
||||
const expires = new Date()
|
||||
expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000)
|
||||
@ -105,24 +113,54 @@ function getCookie(name: string): string | null {
|
||||
return null
|
||||
}
|
||||
|
||||
function updateVisibleList() {
|
||||
visibleEpisodeList.value = allEpisodeList.value.slice(0, currentVisibleCount.value)
|
||||
function initPlyr() {
|
||||
if (!videoRef.value) return
|
||||
|
||||
if (plyrInstance) {
|
||||
plyrInstance.destroy()
|
||||
plyrInstance = null
|
||||
}
|
||||
|
||||
plyrInstance = new Plyr(videoRef.value, {
|
||||
controls: [
|
||||
'play-large',
|
||||
'restart',
|
||||
'rewind',
|
||||
'play',
|
||||
'fast-forward',
|
||||
'progress',
|
||||
'current-time',
|
||||
'duration',
|
||||
'mute',
|
||||
'volume',
|
||||
'settings',
|
||||
'pip',
|
||||
'airplay',
|
||||
'fullscreen'
|
||||
],
|
||||
settings: ['speed'],
|
||||
speed: {
|
||||
selected: 1,
|
||||
options: [0.5, 0.75, 1, 1.25, 1.5, 2]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function loadMoreEpisodes() {
|
||||
currentVisibleCount.value += pageSize
|
||||
updateVisibleList()
|
||||
}
|
||||
function destroyPlayer() {
|
||||
if (hlsInstance) {
|
||||
hlsInstance.destroy()
|
||||
hlsInstance = null
|
||||
}
|
||||
|
||||
function destroyHls(): void {
|
||||
if (hlsRef.value) {
|
||||
hlsRef.value.destroy()
|
||||
hlsRef.value = null
|
||||
if (plyrInstance) {
|
||||
plyrInstance.destroy()
|
||||
plyrInstance = null
|
||||
}
|
||||
|
||||
if (videoRef.value) {
|
||||
videoRef.value.ontimeupdate = null
|
||||
videoRef.value.onloadedmetadata = null
|
||||
videoRef.value.src = ''
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,51 +173,82 @@ function bindProgressSave(video: HTMLVideoElement, item: EpisodeData) {
|
||||
function restoreProgress(video: HTMLVideoElement, item: EpisodeData) {
|
||||
const savedTime = getCookie(`qishier_progress_${item.id}`)
|
||||
if (savedTime && !Number.isNaN(Number(savedTime))) {
|
||||
video.currentTime = Number(savedTime)
|
||||
const time = Number(savedTime)
|
||||
if (time >= 0) {
|
||||
video.currentTime = time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchList(): Promise<void> {
|
||||
async function fetchFirstPage(): Promise<void> {
|
||||
try {
|
||||
loading.value = true
|
||||
errorMsg.value = ''
|
||||
const res = await getAllQishierList()
|
||||
const data = res.data
|
||||
|
||||
console.log('前端拿到缓存:', {
|
||||
updatedAt: data.updatedAt,
|
||||
total: data.total,
|
||||
pages: Object.keys(data.pages || {}).length,
|
||||
listLength: Array.isArray(data.list) ? data.list.length : 0
|
||||
})
|
||||
const res = await getQishierList(1, pageSize.value)
|
||||
const data = res.data
|
||||
|
||||
columnInfo.name = data.name || '七十二家房客'
|
||||
columnInfo.coverUrl = data.coverUrl || ''
|
||||
updatedAt.value = data.updatedAt || null
|
||||
total.value = data.total || 0
|
||||
currentPage.value = data.page || 1
|
||||
hasMore.value = !!data.hasMore
|
||||
episodeList.value = Array.isArray(data.list) ? data.list : []
|
||||
|
||||
allEpisodeList.value = Array.isArray(data.list) ? data.list : []
|
||||
currentVisibleCount.value = pageSize
|
||||
updateVisibleList()
|
||||
|
||||
if (allEpisodeList.value.length > 0) {
|
||||
if (episodeList.value.length > 0) {
|
||||
const savedEpisodeId = getCookie('qishier_current_episode_id')
|
||||
const savedEpisode = allEpisodeList.value.find((item) => item.id === savedEpisodeId)
|
||||
const savedEpisode = episodeList.value.find((item) => item.id === savedEpisodeId)
|
||||
const firstEpisode = episodeList.value[0]
|
||||
|
||||
if (savedEpisode) {
|
||||
const index = allEpisodeList.value.findIndex((item) => item.id === savedEpisode.id)
|
||||
if (index >= 0 && index + 1 > currentVisibleCount.value) {
|
||||
currentVisibleCount.value = Math.ceil((index + 1) / pageSize) * pageSize
|
||||
updateVisibleList()
|
||||
}
|
||||
await playEpisode(savedEpisode)
|
||||
} else {
|
||||
await playEpisode(allEpisodeList.value[0])
|
||||
} else if (firstEpisode) {
|
||||
await playEpisode(firstEpisode)
|
||||
}
|
||||
} else {
|
||||
errorMsg.value = '缓存里还没有节目数据,请先运行采集脚本'
|
||||
errorMsg.value = '暂无节目数据'
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('fetchList error:', error)
|
||||
errorMsg.value = error?.message || '读取缓存失败'
|
||||
console.error('fetchFirstPage error:', error)
|
||||
errorMsg.value = error?.message || '读取数据失败'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMoreEpisodes(): Promise<void> {
|
||||
if (!hasMore.value || loadingMore.value) return
|
||||
|
||||
try {
|
||||
loadingMore.value = true
|
||||
const nextPage = currentPage.value + 1
|
||||
const res = await getQishierList(nextPage, pageSize.value)
|
||||
const data = res.data
|
||||
|
||||
const oldIds = new Set(episodeList.value.map((item) => item.id))
|
||||
const newItems = (data.list || []).filter((item) => !oldIds.has(item.id))
|
||||
|
||||
episodeList.value.push(...newItems)
|
||||
currentPage.value = data.page || nextPage
|
||||
hasMore.value = !!data.hasMore
|
||||
total.value = data.total || total.value
|
||||
updatedAt.value = data.updatedAt || updatedAt.value
|
||||
} catch (error: any) {
|
||||
console.error('loadMoreEpisodes error:', error)
|
||||
errorMsg.value = error?.message || '加载更多失败'
|
||||
} finally {
|
||||
loadingMore.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRefresh(): Promise<void> {
|
||||
try {
|
||||
await refreshQishierCache()
|
||||
await fetchFirstPage()
|
||||
} catch (error: any) {
|
||||
console.error('handleRefresh error:', error)
|
||||
errorMsg.value = error?.message || '刷新失败'
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,7 +262,8 @@ async function playEpisode(item: EpisodeData): Promise<void> {
|
||||
const video = videoRef.value
|
||||
if (!video) return
|
||||
|
||||
destroyHls()
|
||||
destroyPlayer()
|
||||
initPlyr()
|
||||
|
||||
if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
video.src = item.videoUrl
|
||||
@ -201,29 +271,27 @@ async function playEpisode(item: EpisodeData): Promise<void> {
|
||||
|
||||
video.onloadedmetadata = () => {
|
||||
restoreProgress(video, item)
|
||||
}
|
||||
|
||||
bindProgressSave(video, item)
|
||||
void video.play().catch(() => {})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (Hls.isSupported()) {
|
||||
const hls = new Hls()
|
||||
hls.loadSource(item.videoUrl)
|
||||
hls.attachMedia(video)
|
||||
hlsInstance = new Hls()
|
||||
hlsInstance.loadSource(item.videoUrl)
|
||||
hlsInstance.attachMedia(video)
|
||||
|
||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||
hlsInstance.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||
restoreProgress(video, item)
|
||||
bindProgressSave(video, item)
|
||||
void video.play().catch(() => {})
|
||||
})
|
||||
|
||||
hls.on(Hls.Events.ERROR, () => {
|
||||
hlsInstance.on(Hls.Events.ERROR, () => {
|
||||
errorMsg.value = '视频播放失败'
|
||||
})
|
||||
|
||||
hlsRef.value = hls
|
||||
} else {
|
||||
errorMsg.value = '当前浏览器不支持 m3u8 播放'
|
||||
}
|
||||
@ -246,11 +314,11 @@ function formatDuration(seconds: number): string {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void fetchList()
|
||||
void fetchFirstPage()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
destroyHls()
|
||||
destroyPlayer()
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -286,6 +354,7 @@ onBeforeUnmount(() => {
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
@ -295,6 +364,7 @@ onBeforeUnmount(() => {
|
||||
background: #333;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
@ -409,6 +479,15 @@ onBeforeUnmount(() => {
|
||||
background: #444;
|
||||
}
|
||||
|
||||
.load-more-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
:deep(.plyr) {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.layout {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@ -2,22 +2,55 @@ import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
export default defineConfig({
|
||||
export default defineConfig(({ command }) => ({
|
||||
plugins: [vue()],
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
},
|
||||
|
||||
server: {
|
||||
port: 5173,
|
||||
port: 23811,
|
||||
host: true,
|
||||
open: true,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:3000',
|
||||
target: 'http://127.0.0.1:23822/',
|
||||
//target: 'http://8.134.120.132:23822/',
|
||||
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// 打包配置
|
||||
build: {
|
||||
// https://vite.dev/config/build-options.html
|
||||
sourcemap: command === 'build' ? false : 'inline',
|
||||
target: 'es2018',
|
||||
|
||||
outDir: 'dist',
|
||||
assetsDir: 'assets',
|
||||
|
||||
chunkSizeWarningLimit: 2000,
|
||||
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// JS 拆分
|
||||
chunkFileNames: 'static/js/[name]-[hash].js',
|
||||
entryFileNames: 'static/js/[name]-[hash].js',
|
||||
|
||||
// 资源文件
|
||||
assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
|
||||
|
||||
// 手动拆包
|
||||
manualChunks: {
|
||||
vue: ['vue'],
|
||||
hls: ['hls.js']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user