diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..78a752d --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +.DS_Store +node_modules/ +dist/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +**/*.log + +tests/**/coverage/ +tests/e2e/reports +selenium-debug.log + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.local + +package-lock.json +yarn.lock diff --git a/README.md b/README.md index 4769574..c69f61e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 72 +# 72QishierPlayer This template should help get you started developing with Vue 3 in Vite. diff --git a/backServer/ku b/backServer/ku new file mode 100644 index 0000000..bd1bfb3 --- /dev/null +++ b/backServer/ku @@ -0,0 +1 @@ +express cors axios diff --git a/backServer/package.json b/backServer/package.json new file mode 100644 index 0000000..024b38a --- /dev/null +++ b/backServer/package.json @@ -0,0 +1,14 @@ +{ + "name": "qishier-backserver", + "version": "1.0.0", + "type": "module", + "scripts": { + "capture": "node capture.js", + "server": "node server.js" + }, + "dependencies": { + "cors": "^2.8.5", + "express": "^5.1.0", + "playwright": "^1.52.0" + } +} diff --git a/backServer/server.js b/backServer/server.js new file mode 100644 index 0000000..03d1e6f --- /dev/null +++ b/backServer/server.js @@ -0,0 +1,46 @@ +import express from 'express' +import cors from 'cors' +import { ensureDataFiles, readCache, readStatus } from './lib/cache.js' + +const app = express() +app.use(cors()) +app.use(express.json()) + +ensureDataFiles() + +app.get('/api/qishier/all', (_req, res) => { + try { + const cache = readCache() + res.json({ + updatedAt: cache.updatedAt, + name: cache.name || '七十二家房客', + coverUrl: cache.coverUrl || '', + displayType: cache.displayType || 0, + total: cache.items?.length || 0, + beginScoreMap: cache.beginScoreMap || {}, + pages: cache.pages || {}, + list: cache.items || [] + }) + } catch (error) { + res.status(500).json({ + message: '读取缓存失败', + error: error.message + }) + } +}) + +app.get('/api/qishier/status', (_req, res) => { + try { + const status = readStatus() + res.json(status) + } catch (error) { + res.status(500).json({ + message: '读取状态失败', + error: error.message + }) + } +}) + +app.listen(3000, () => { + console.log('Node 服务已启动: http://localhost:3000') +}) diff --git a/backServer/test.js b/backServer/test.js new file mode 100644 index 0000000..e006a9b --- /dev/null +++ b/backServer/test.js @@ -0,0 +1,93 @@ +import express from 'express' +import cors from 'cors' +import axios from 'axios' + +const app = express() +app.use(cors()) + +const beginScoreMap = { + 1: 0, + 2: 1765202160000, + 3: 1764338100000, + 4: 1763561820000, + 5: 1762962000000, + 6: 1762352220000, + 7: 1761746220000, + 8: 1761056280000, + 9: 1740315600000, + 10: 1738414800000, + 11: 1736514000000, + 12: 1734526800000, + 13: 1732714295000, + 14: 1730986358000, + 15: 1729171974000, + 16: 1727442000000, + 17: 1725713999999, + 18: 1723899600000, + 19: 1721998800000, + 20: 1720186339000, + 21: 1718371929000, + 22: 1716641999999, + 23: 1714827600000, + 24: 1713099599999, + 25: 1711372007000, + 26: 1709559052000 +} + +function buildHeaders() { + return { + Accept: 'application/json, text/plain, */*', + 'Content-Type': 'application/json', + Origin: 'https://www1.gdtv.cn', + Referer: 'https://www1.gdtv.cn/', + 'User-Agent': + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36', + 'x-itouchtv-ca-key': '89541943007407288657755311868534', + 'x-itouchtv-ca-signature': 'UPNSWKuT1dzRU9WYvdgChNmjU0FdmwSPmwxnmahc+Bo=', + 'x-itouchtv-ca-timestamp': '1773166348175', + 'x-itouchtv-client': 'WEB_PC', + 'x-itouchtv-device-id': 'WEB_c5bf71d0-1c9c-11f1-9b47-2fe652348943' + } +} + +app.get('/api/qishier', async (req, res) => { + const page = Number(req.query.page || 26) + const pageSize = Number(req.query.pageSize || 40) + const beginScore = beginScoreMap[page] + + try { + if (beginScore === undefined) { + return res.status(400).json({ + message: `第 ${page} 页缺少 beginScore` + }) + } + + const response = await axios.get( + 'https://gdtv-api.gdtv.cn/api/tvColumn/v1/news', + { + params: { + pageSize, + currentPage: page, + searchByTime: false, + tvColumnPk: 768, + beginScore + }, + headers: buildHeaders(), + timeout: 15000 + } + ) + + res.json(response.data) + } catch (error) { + console.error('分页请求失败:', error?.response?.status, error?.response?.data || error.message) + res.status(error?.response?.status || 500).json({ + message: '分页请求失败', + status: error?.response?.status || 500, + data: error?.response?.data || null + }) + } +}) + +app.listen(3000, () => { + console.log('代理服务已启动: http://localhost:3000') +}) diff --git a/env.d.ts b/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/index.html b/index.html new file mode 100644 index 0000000..9e5fc8f --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..5a5f9e0 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "72qishierplayer", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build" + }, + "dependencies": { + "axios": "^1.13.6", + "cors": "^2.8.6", + "express": "^5.2.1", + "hls.js": "^1.6.15", + "vue": "^3.5.29" + }, + "devDependencies": { + "@tsconfig/node24": "^24.0.4", + "@types/node": "^24.11.0", + "@vitejs/plugin-vue": "^6.0.4", + "@vue/tsconfig": "^0.8.1", + "npm-run-all2": "^8.0.4", + "typescript": "~5.9.3", + "vite": "^7.3.1", + "vite-plugin-vue-devtools": "^8.0.6", + "vue-tsc": "^3.2.5" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/public/favicon.ico differ diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..f97ec84 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/src/api/qishier.ts b/src/api/qishier.ts new file mode 100644 index 0000000..a59088c --- /dev/null +++ b/src/api/qishier.ts @@ -0,0 +1,34 @@ +import axios from 'axios' + +export interface EpisodeData { + id: string + title: string + coverUrl: string + releasedAt: number + timeLength: number + videoUrl: string + raw?: Record +} + +export interface QishierCacheResponse { + updatedAt: string | null + name: string + coverUrl: string + displayType: number + total: number + beginScoreMap: Record + pages: Record + list: EpisodeData[] +} + +export function getAllQishierList() { + return axios.get('/api/qishier/all', { + timeout: 15000 + }) +} + +export function getQishierStatus() { + return axios.get('/api/qishier/status', { + timeout: 15000 + }) +} diff --git a/src/assets/base.css b/src/assets/base.css new file mode 100644 index 0000000..8816868 --- /dev/null +++ b/src/assets/base.css @@ -0,0 +1,86 @@ +/* color palette from */ +:root { + --vt-c-white: #ffffff; + --vt-c-white-soft: #f8f8f8; + --vt-c-white-mute: #f2f2f2; + + --vt-c-black: #181818; + --vt-c-black-soft: #222222; + --vt-c-black-mute: #282828; + + --vt-c-indigo: #2c3e50; + + --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); + --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); + --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); + --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); + + --vt-c-text-light-1: var(--vt-c-indigo); + --vt-c-text-light-2: rgba(60, 60, 60, 0.66); + --vt-c-text-dark-1: var(--vt-c-white); + --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); +} + +/* semantic color variables for this project */ +:root { + --color-background: var(--vt-c-white); + --color-background-soft: var(--vt-c-white-soft); + --color-background-mute: var(--vt-c-white-mute); + + --color-border: var(--vt-c-divider-light-2); + --color-border-hover: var(--vt-c-divider-light-1); + + --color-heading: var(--vt-c-text-light-1); + --color-text: var(--vt-c-text-light-1); + + --section-gap: 160px; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-background: var(--vt-c-black); + --color-background-soft: var(--vt-c-black-soft); + --color-background-mute: var(--vt-c-black-mute); + + --color-border: var(--vt-c-divider-dark-2); + --color-border-hover: var(--vt-c-divider-dark-1); + + --color-heading: var(--vt-c-text-dark-1); + --color-text: var(--vt-c-text-dark-2); + } +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + font-weight: normal; +} + +body { + min-height: 100vh; + color: var(--color-text); + background: var(--color-background); + transition: + color 0.5s, + background-color 0.5s; + line-height: 1.6; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; + font-size: 15px; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/src/assets/logo.svg b/src/assets/logo.svg new file mode 100644 index 0000000..7565660 --- /dev/null +++ b/src/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/src/assets/main.css b/src/assets/main.css new file mode 100644 index 0000000..36fb845 --- /dev/null +++ b/src/assets/main.css @@ -0,0 +1,35 @@ +@import './base.css'; + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + font-weight: normal; +} + +a, +.green { + text-decoration: none; + color: hsla(160, 100%, 37%, 1); + transition: 0.4s; + padding: 3px; +} + +@media (hover: hover) { + a:hover { + background-color: hsla(160, 100%, 37%, 0.2); + } +} + +@media (min-width: 1024px) { + body { + display: flex; + place-items: center; + } + + #app { + display: grid; + grid-template-columns: 1fr 1fr; + padding: 0 2rem; + } +} diff --git a/src/components/HelloWorld.vue b/src/components/HelloWorld.vue new file mode 100644 index 0000000..a2eabd1 --- /dev/null +++ b/src/components/HelloWorld.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/components/TheWelcome.vue b/src/components/TheWelcome.vue new file mode 100644 index 0000000..8b731d9 --- /dev/null +++ b/src/components/TheWelcome.vue @@ -0,0 +1,95 @@ + + + diff --git a/src/components/WelcomeItem.vue b/src/components/WelcomeItem.vue new file mode 100644 index 0000000..6d7086a --- /dev/null +++ b/src/components/WelcomeItem.vue @@ -0,0 +1,87 @@ + + + diff --git a/src/components/icons/IconCommunity.vue b/src/components/icons/IconCommunity.vue new file mode 100644 index 0000000..2dc8b05 --- /dev/null +++ b/src/components/icons/IconCommunity.vue @@ -0,0 +1,7 @@ + diff --git a/src/components/icons/IconDocumentation.vue b/src/components/icons/IconDocumentation.vue new file mode 100644 index 0000000..6d4791c --- /dev/null +++ b/src/components/icons/IconDocumentation.vue @@ -0,0 +1,7 @@ + diff --git a/src/components/icons/IconEcosystem.vue b/src/components/icons/IconEcosystem.vue new file mode 100644 index 0000000..c3a4f07 --- /dev/null +++ b/src/components/icons/IconEcosystem.vue @@ -0,0 +1,7 @@ + diff --git a/src/components/icons/IconSupport.vue b/src/components/icons/IconSupport.vue new file mode 100644 index 0000000..7452834 --- /dev/null +++ b/src/components/icons/IconSupport.vue @@ -0,0 +1,7 @@ + diff --git a/src/components/icons/IconTooling.vue b/src/components/icons/IconTooling.vue new file mode 100644 index 0000000..660598d --- /dev/null +++ b/src/components/icons/IconTooling.vue @@ -0,0 +1,19 @@ + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..fe5bae3 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './style.css' + +createApp(App).mount('#app') diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..b0b8a04 --- /dev/null +++ b/src/style.css @@ -0,0 +1,14 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; +} + +* { + box-sizing: border-box; +} + +html, +body, +#app { + margin: 0; + min-height: 100%; +} diff --git a/src/views/QishierPlayer.vue b/src/views/QishierPlayer.vue new file mode 100644 index 0000000..73a3d16 --- /dev/null +++ b/src/views/QishierPlayer.vue @@ -0,0 +1,248 @@ + + + + + diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..c0f2d86 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,18 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + // Extra safety for array and object lookups, but may have false positives. + "noUncheckedIndexedAccess": true, + + // Path mapping for cleaner imports. + "paths": { + "@/*": ["./src/*"] + }, + + // `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking. + // Specified here to keep it out of the root directory. + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..5988839 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,28 @@ +// TSConfig for modules that run in Node.js environment via either transpilation or type-stripping. +{ + "extends": "@tsconfig/node24/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*", + "eslint.config.*" + ], + "compilerOptions": { + // Most tools use transpilation instead of Node.js's native type-stripping. + // Bundler mode provides a smoother developer experience. + "module": "preserve", + "moduleResolution": "bundler", + + // Include Node.js types and avoid accidentally including other `@types/*` packages. + "types": ["node"], + + // Disable emitting output during `vue-tsc --build`, which is used for type-checking only. + "noEmit": true, + + // `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking. + // Specified here to keep it out of the root directory. + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo" + } +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..7cb6c65 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { fileURLToPath, URL } from 'node:url' + +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + }, + server: { + proxy: { + '/api': { + target: 'http://localhost:3000', + changeOrigin: true + } + } + } +})