Compare commits
No commits in common. "36be77948fa2121dbae4fb6354936260838ccfcf" and "d15eb6997c4d2d8f8371415e8b5e88d92e57cb83" have entirely different histories.
36be77948f
...
d15eb6997c
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 563 B After Width: | Height: | Size: 563 B |
|
Before Width: | Height: | Size: 800 B After Width: | Height: | Size: 800 B |
|
Before Width: | Height: | Size: 447 B After Width: | Height: | Size: 447 B |
@ -277,9 +277,6 @@ class DouyinAutoScheduler:
|
|||||||
"data": []
|
"data": []
|
||||||
}
|
}
|
||||||
|
|
||||||
# 获取Rankings_management集合用于补充详细信息
|
|
||||||
rankings_management_collection = db['Rankings_management']
|
|
||||||
|
|
||||||
# 生成排序后的榜单数据
|
# 生成排序后的榜单数据
|
||||||
for i, item in enumerate(videos_with_growth, 1):
|
for i, item in enumerate(videos_with_growth, 1):
|
||||||
video = item["video"]
|
video = item["video"]
|
||||||
@ -293,46 +290,16 @@ class DouyinAutoScheduler:
|
|||||||
yesterday_rank = item["yesterday_data"].get("rank", 0)
|
yesterday_rank = item["yesterday_data"].get("rank", 0)
|
||||||
rank_change = yesterday_rank - i
|
rank_change = yesterday_rank - i
|
||||||
|
|
||||||
# 🔍 从Rankings_management获取详细信息
|
|
||||||
management_data = rankings_management_collection.find_one({"mix_name": mix_name})
|
|
||||||
|
|
||||||
ranking_item = {
|
ranking_item = {
|
||||||
# 🎯 核心榜单字段
|
|
||||||
"rank": i,
|
"rank": i,
|
||||||
"title": mix_name,
|
"title": mix_name,
|
||||||
"mix_name": mix_name, # 确保包含mix_name字段用于同步
|
|
||||||
"play_vv": current_play_vv,
|
"play_vv": current_play_vv,
|
||||||
"series_author": video.get("series_author", ""),
|
"author": video.get("author", ""),
|
||||||
"video_id": video_id,
|
"video_id": video_id,
|
||||||
"video_url": video.get("video_url", ""),
|
"video_url": video.get("video_url", ""),
|
||||||
"cover_image_url": video.get("cover_image_url", ""),
|
"cover_image_url": video.get("cover_image_url", ""),
|
||||||
"playcount_str": video.get("playcount", ""),
|
"playcount_str": video.get("playcount", ""),
|
||||||
|
# 时间轴对比数据
|
||||||
# 📋 从Rankings_management获取的详细字段
|
|
||||||
"batch_id": management_data.get("batch_id", "") if management_data else "",
|
|
||||||
"batch_time": management_data.get("batch_time") if management_data else None,
|
|
||||||
"item_sequence": management_data.get("item_sequence", 0) if management_data else 0,
|
|
||||||
"mix_id": management_data.get("mix_id", "") if management_data else "",
|
|
||||||
"playcount": management_data.get("playcount", "") if management_data else "",
|
|
||||||
"request_id": management_data.get("request_id", "") if management_data else "",
|
|
||||||
"cover_image_url_original": management_data.get("cover_image_url_original", "") if management_data else "",
|
|
||||||
"cover_upload_success": management_data.get("cover_upload_success", True) if management_data else True,
|
|
||||||
"cover_backup_urls": management_data.get("cover_backup_urls", []) if management_data else [],
|
|
||||||
"desc": management_data.get("desc", "") if management_data else "",
|
|
||||||
"updated_to_episode": management_data.get("updated_to_episode", 0) if management_data else 0,
|
|
||||||
"episode_video_ids": management_data.get("episode_video_ids", []) if management_data else [],
|
|
||||||
"episode_details": management_data.get("episode_details", []) if management_data else [],
|
|
||||||
"data_status": management_data.get("data_status", "") if management_data else "",
|
|
||||||
"realtime_saved": management_data.get("realtime_saved", True) if management_data else True,
|
|
||||||
"created_at": management_data.get("created_at") if management_data else None,
|
|
||||||
"last_updated": management_data.get("last_updated") if management_data else None,
|
|
||||||
"Manufacturing_Field": management_data.get("Manufacturing_Field", "") if management_data else "",
|
|
||||||
"Copyright_field": management_data.get("Copyright_field", "") if management_data else "",
|
|
||||||
"Novel_IDs": management_data.get("Novel_IDs", []) if management_data else [],
|
|
||||||
"Anime_IDs": management_data.get("Anime_IDs", []) if management_data else [],
|
|
||||||
"Drama_IDs": management_data.get("Drama_IDs", []) if management_data else [],
|
|
||||||
|
|
||||||
# 📊 时间轴对比数据(重要:包含播放量差值)
|
|
||||||
"timeline_data": {
|
"timeline_data": {
|
||||||
"is_new": item["is_new"],
|
"is_new": item["is_new"],
|
||||||
"rank_change": rank_change,
|
"rank_change": rank_change,
|
||||||
@ -363,29 +330,6 @@ class DouyinAutoScheduler:
|
|||||||
logging.info(f"📝 创建了新的今日榜单数据(第{existing_count + 1}次计算,包含最新差值)")
|
logging.info(f"📝 创建了新的今日榜单数据(第{existing_count + 1}次计算,包含最新差值)")
|
||||||
logging.info(f"🔖 计算ID: {comprehensive_ranking['calculation_id']}")
|
logging.info(f"🔖 计算ID: {comprehensive_ranking['calculation_id']}")
|
||||||
|
|
||||||
# 📊 检查数据完整性:统计从Rankings_management成功获取详细信息的项目数量
|
|
||||||
total_items = len(comprehensive_ranking["data"])
|
|
||||||
items_with_management_data = 0
|
|
||||||
items_with_manufacturing = 0
|
|
||||||
items_with_copyright = 0
|
|
||||||
|
|
||||||
for item in comprehensive_ranking["data"]:
|
|
||||||
# 检查是否从Rankings_management获取到了数据
|
|
||||||
if item.get("batch_id") or item.get("desc") or item.get("Manufacturing_Field") or item.get("Copyright_field"):
|
|
||||||
items_with_management_data += 1
|
|
||||||
if item.get("Manufacturing_Field"):
|
|
||||||
items_with_manufacturing += 1
|
|
||||||
if item.get("Copyright_field"):
|
|
||||||
items_with_copyright += 1
|
|
||||||
|
|
||||||
print(f"📊 数据完整性统计:")
|
|
||||||
print(f" 总项目数: {total_items}")
|
|
||||||
print(f" 从Rankings_management获取到详细信息: {items_with_management_data}")
|
|
||||||
print(f" 包含Manufacturing_Field: {items_with_manufacturing}")
|
|
||||||
print(f" 包含Copyright_field: {items_with_copyright}")
|
|
||||||
|
|
||||||
logging.info(f"📊 数据完整性: 总{total_items}项,获取详细信息{items_with_management_data}项,Manufacturing_Field: {items_with_manufacturing},Copyright_field: {items_with_copyright}")
|
|
||||||
|
|
||||||
# 统计信息
|
# 统计信息
|
||||||
new_count = sum(1 for item in comprehensive_ranking["data"] if item["timeline_data"]["is_new"])
|
new_count = sum(1 for item in comprehensive_ranking["data"] if item["timeline_data"]["is_new"])
|
||||||
print(f"✅ 时间轴对比榜单生成成功")
|
print(f"✅ 时间轴对比榜单生成成功")
|
||||||
@ -414,165 +358,13 @@ class DouyinAutoScheduler:
|
|||||||
import traceback
|
import traceback
|
||||||
logging.error(f"详细错误信息: {traceback.format_exc()}")
|
logging.error(f"详细错误信息: {traceback.format_exc()}")
|
||||||
|
|
||||||
def check_and_sync_missing_fields(self):
|
|
||||||
"""实时检查并同步当天缺失字段"""
|
|
||||||
try:
|
|
||||||
from database import db
|
|
||||||
|
|
||||||
# 只检查当天的数据
|
|
||||||
today = date.today()
|
|
||||||
today_str = today.strftime('%Y-%m-%d')
|
|
||||||
|
|
||||||
# 首先检查 Rankings_management 是否有当天的数据
|
|
||||||
rankings_management_collection = db['Rankings_management']
|
|
||||||
management_count = rankings_management_collection.count_documents({})
|
|
||||||
|
|
||||||
if management_count == 0:
|
|
||||||
# Rankings_management 没有数据,说明还没有抓取,直接返回
|
|
||||||
return
|
|
||||||
|
|
||||||
rankings_collection = db['Ranking_storage']
|
|
||||||
key_fields = ['Manufacturing_Field', 'Copyright_field', 'desc', 'series_author']
|
|
||||||
|
|
||||||
# 检查今天是否有缺失字段的数据
|
|
||||||
missing_conditions = []
|
|
||||||
for field in key_fields:
|
|
||||||
missing_conditions.extend([
|
|
||||||
{field: {"$exists": False}},
|
|
||||||
{field: None},
|
|
||||||
{field: ""}
|
|
||||||
])
|
|
||||||
|
|
||||||
today_missing_count = rankings_collection.count_documents({
|
|
||||||
"date": today_str,
|
|
||||||
"$or": missing_conditions
|
|
||||||
})
|
|
||||||
|
|
||||||
# 如果今天没有缺失数据,静默返回
|
|
||||||
if today_missing_count == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
logging.info(f"🔍 检测到今天有 {today_missing_count} 条缺失字段,Rankings_management有 {management_count} 条数据,开始实时同步...")
|
|
||||||
|
|
||||||
# 只处理当天的数据
|
|
||||||
dates_to_check = [today_str]
|
|
||||||
|
|
||||||
total_missing = 0
|
|
||||||
total_synced = 0
|
|
||||||
|
|
||||||
for check_date in dates_to_check:
|
|
||||||
# 查询该日期缺失字段的数据
|
|
||||||
rankings_collection = db['Ranking_storage']
|
|
||||||
|
|
||||||
# 检查多个关键字段(包括新增的分类字段)
|
|
||||||
key_fields = ['Manufacturing_Field', 'Copyright_field', 'desc', 'series_author', 'Novel_IDs', 'Anime_IDs', 'Drama_IDs']
|
|
||||||
missing_conditions = []
|
|
||||||
|
|
||||||
for field in key_fields:
|
|
||||||
missing_conditions.extend([
|
|
||||||
{field: {"$exists": False}},
|
|
||||||
{field: None},
|
|
||||||
{field: ""}
|
|
||||||
])
|
|
||||||
|
|
||||||
missing_query = {
|
|
||||||
"date": check_date,
|
|
||||||
"$or": missing_conditions
|
|
||||||
}
|
|
||||||
|
|
||||||
missing_count = rankings_collection.count_documents(missing_query)
|
|
||||||
|
|
||||||
# 详细统计每个字段的缺失情况
|
|
||||||
field_stats = {}
|
|
||||||
total_items = rankings_collection.count_documents({"date": check_date})
|
|
||||||
|
|
||||||
for field in key_fields:
|
|
||||||
missing_field_count = rankings_collection.count_documents({
|
|
||||||
"date": check_date,
|
|
||||||
"$or": [
|
|
||||||
{field: {"$exists": False}},
|
|
||||||
{field: None},
|
|
||||||
{field: ""}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
field_stats[field] = {
|
|
||||||
"missing": missing_field_count,
|
|
||||||
"completion_rate": ((total_items - missing_field_count) / total_items * 100) if total_items > 0 else 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if missing_count > 0:
|
|
||||||
logging.info(f"📅 今日({check_date}): 发现 {missing_count} 条记录缺失字段(总计 {total_items} 条)")
|
|
||||||
|
|
||||||
# 输出详细的字段统计
|
|
||||||
for field, stats in field_stats.items():
|
|
||||||
if stats["missing"] > 0:
|
|
||||||
logging.info(f" - {field}: 缺失 {stats['missing']} 条 ({stats['completion_rate']:.1f}% 完整)")
|
|
||||||
|
|
||||||
total_missing += missing_count
|
|
||||||
|
|
||||||
# 尝试同步
|
|
||||||
try:
|
|
||||||
from routers.rank_api_routes import sync_ranking_storage_fields
|
|
||||||
|
|
||||||
# 使用改进的重试机制
|
|
||||||
sync_result = sync_ranking_storage_fields(
|
|
||||||
target_date=check_date,
|
|
||||||
force_update=False,
|
|
||||||
max_retries=2, # 定期检查时重试2次
|
|
||||||
retry_delay=15 # 15秒重试间隔
|
|
||||||
)
|
|
||||||
|
|
||||||
if sync_result.get("success", False):
|
|
||||||
stats = sync_result.get("stats", {})
|
|
||||||
synced = stats.get("updated_items", 0)
|
|
||||||
retry_count = stats.get("retry_count", 0)
|
|
||||||
pending_final = stats.get("pending_items_final", 0)
|
|
||||||
|
|
||||||
total_synced += synced
|
|
||||||
if synced > 0:
|
|
||||||
logging.info(f"✅ 今日({check_date}): 成功同步 {synced} 条记录")
|
|
||||||
|
|
||||||
if retry_count > 0:
|
|
||||||
logging.info(f"🔄 今日({check_date}): 使用了 {retry_count} 次重试")
|
|
||||||
|
|
||||||
if pending_final > 0:
|
|
||||||
logging.warning(f"⚠️ 今日({check_date}): {pending_final} 条记录在 Rankings_management 中仍未找到")
|
|
||||||
else:
|
|
||||||
logging.warning(f"⚠️ 今日({check_date}): 同步失败 - {sync_result.get('message', '')}")
|
|
||||||
|
|
||||||
except Exception as sync_error:
|
|
||||||
logging.error(f"💥 今日({check_date}): 同步过程出错 - {sync_error}")
|
|
||||||
else:
|
|
||||||
if total_items > 0:
|
|
||||||
logging.info(f"📅 {check_date}: 所有字段完整(总计 {total_items} 条记录)")
|
|
||||||
# 显示完整性统计
|
|
||||||
for field, stats in field_stats.items():
|
|
||||||
logging.info(f" - {field}: {stats['completion_rate']:.1f}% 完整")
|
|
||||||
else:
|
|
||||||
logging.info(f"📅 {check_date}: 无数据")
|
|
||||||
|
|
||||||
if total_missing > 0:
|
|
||||||
logging.info(f"🔍 当天同步完成:发现 {total_missing} 条缺失记录,成功同步 {total_synced} 条")
|
|
||||||
print(f"🔍 当天字段同步:发现 {total_missing} 条缺失,同步 {total_synced} 条")
|
|
||||||
else:
|
|
||||||
# 当天没有缺失数据时,不输出日志(静默模式)
|
|
||||||
pass
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"💥 检查缺失字段时发生异常: {e}")
|
|
||||||
import traceback
|
|
||||||
logging.error(f"详细错误信息: {traceback.format_exc()}")
|
|
||||||
|
|
||||||
def setup_schedule(self):
|
def setup_schedule(self):
|
||||||
"""设置定时任务"""
|
"""设置定时任务"""
|
||||||
# 每小时的整点执行抖音播放量抓取
|
# 每小时的整点执行抖音播放量抓取
|
||||||
schedule.every().hour.at(":00").do(self.run_douyin_scraper)
|
schedule.every().hour.at(":00").do(self.run_douyin_scraper)
|
||||||
|
|
||||||
# 每1分钟检查一次缺失字段并尝试同步(实时同步)
|
|
||||||
schedule.every(1).minutes.do(self.check_and_sync_missing_fields)
|
|
||||||
|
|
||||||
logging.info(f"⏰ 定时器已设置:每小时整点执行抖音播放量抓取")
|
logging.info(f"⏰ 定时器已设置:每小时整点执行抖音播放量抓取")
|
||||||
logging.info(f"⏰ 定时器已设置:每1分钟检查缺失字段并同步(实时模式)")
|
|
||||||
|
|
||||||
def show_next_run(self):
|
def show_next_run(self):
|
||||||
"""显示下次执行时间"""
|
"""显示下次执行时间"""
|
||||||
|
|||||||
@ -143,13 +143,9 @@
|
|||||||
{
|
{
|
||||||
"video_id": "7558378239337467174",
|
"video_id": "7558378239337467174",
|
||||||
"episode_num": 0
|
"episode_num": 0
|
||||||
},
|
|
||||||
{
|
|
||||||
"video_id": "7567050545257516331",
|
|
||||||
"episode_num": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"total_count": 37,
|
"total_count": 36,
|
||||||
"last_update": "2025-10-31T09:50:18.533027",
|
"last_update": "2025-10-22T09:55:32.073567",
|
||||||
"mix_name": "末世系列"
|
"mix_name": "末世系列"
|
||||||
}
|
}
|
||||||
BIN
frontend/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
5
frontend/public/placeholder-poster.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="60" height="80" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="60" height="80" fill="#f0f0f0" stroke="#ddd" stroke-width="1"/>
|
||||||
|
<text x="30" y="35" text-anchor="middle" font-family="Arial" font-size="8" fill="#999">暂无</text>
|
||||||
|
<text x="30" y="50" text-anchor="middle" font-family="Arial" font-size="8" fill="#999">图片</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 355 B |
@ -1,11 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const currentTab = ref('ranking') // 'ranking' 或 'news'
|
const currentTab = ref('ranking') // 'ranking' 或 'news'
|
||||||
const rankingData = ref([])
|
const rankingData = ref([])
|
||||||
@ -63,22 +59,14 @@ const generateDateOptions = () => {
|
|||||||
const fetchRankingData = async () => {
|
const fetchRankingData = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
// 构建API参数
|
const response = await axios.get('http://localhost:5001/api/rank/videos', {
|
||||||
const params = {
|
params: {
|
||||||
page: currentPage.value,
|
page: currentPage.value,
|
||||||
limit: 100,
|
limit: 20,
|
||||||
sort: 'growth',
|
sort: 'growth',
|
||||||
start_date: selectedDate.value,
|
start_date: selectedDate.value,
|
||||||
end_date: selectedDate.value
|
end_date: selectedDate.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果选择了特定分类,添加分类参数
|
|
||||||
if (selectedCategory.value !== 'all') {
|
|
||||||
params.classification_type = selectedCategory.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await axios.get('http://localhost:5001/api/rank/videos', {
|
|
||||||
params: params
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.data.success) {
|
if (response.data.success) {
|
||||||
@ -86,7 +74,6 @@ const fetchRankingData = async () => {
|
|||||||
totalPages.value = response.data.pagination.pages
|
totalPages.value = response.data.pagination.pages
|
||||||
// 获取后端返回的更新时间
|
// 获取后端返回的更新时间
|
||||||
updateTime.value = response.data.update_time || ''
|
updateTime.value = response.data.update_time || ''
|
||||||
console.log(`获取${selectedCategory.value === 'all' ? '全部' : selectedCategory.value}分类数据成功,共${response.data.data.length}条`)
|
|
||||||
} else {
|
} else {
|
||||||
console.error('获取数据失败:', response.data.message)
|
console.error('获取数据失败:', response.data.message)
|
||||||
rankingData.value = []
|
rankingData.value = []
|
||||||
@ -225,9 +212,7 @@ const selectDate = (dateValue) => {
|
|||||||
// 切换分类
|
// 切换分类
|
||||||
const switchCategory = (category) => {
|
const switchCategory = (category) => {
|
||||||
selectedCategory.value = category
|
selectedCategory.value = category
|
||||||
currentPage.value = 1 // 重置页码
|
// 这里可以添加分类筛选逻辑
|
||||||
fetchRankingData() // 重新获取数据
|
|
||||||
console.log(`切换到分类: ${category}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化日期显示(用于日榜标题)
|
// 格式化日期显示(用于日榜标题)
|
||||||
@ -252,11 +237,6 @@ const getRankBadgeClass = (rank) => {
|
|||||||
return 'rank-normal'
|
return 'rank-normal'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 跳转到后台管理
|
|
||||||
const goToAdmin = () => {
|
|
||||||
router.push('/admin')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面加载时初始化
|
// 页面加载时初始化
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initDate()
|
initDate()
|
||||||
@ -266,29 +246,27 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="app">
|
<div class="app">
|
||||||
<!-- 路由视图 -->
|
<!-- 主容器 -->
|
||||||
<router-view v-if="route.path !== '/'" />
|
<div class="main-container">
|
||||||
|
|
||||||
<!-- 主容器 - 仅在首页显示 -->
|
|
||||||
<div v-if="route.path === '/'" class="main-container">
|
|
||||||
<!-- 顶部标题区域 -->
|
<!-- 顶部标题区域 -->
|
||||||
<div class="header-section">
|
<div class="header-section">
|
||||||
<div class="title-wrapper">
|
<div class="title-wrapper">
|
||||||
|
<div class="title-icon-left"></div>
|
||||||
<h1 class="main-title">AI棒榜</h1>
|
<h1 class="main-title">AI棒榜</h1>
|
||||||
<button class="admin-btn" @click="goToAdmin">管理</button>
|
<div class="title-icon-right"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 横幅区域 -->
|
<!-- 横幅区域 -->
|
||||||
<!-- <div class="banner-section">
|
<div class="banner-section">
|
||||||
<div class="banner-content">
|
<div class="banner-content">
|
||||||
<p class="banner-subtitle">微短剧爆火</p>
|
<p class="banner-subtitle">微短剧爆火</p>
|
||||||
<p class="banner-title">中国"血统"的ReelShort征服美国</p>
|
<p class="banner-title">中国"血统"的ReelShort征服美国</p>
|
||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div>
|
||||||
|
|
||||||
<!-- 装饰分隔线 -->
|
<!-- 装饰分隔线 -->
|
||||||
<!-- <div class="divider-dots"></div> -->
|
<div class="divider-dots"></div>
|
||||||
|
|
||||||
<!-- 日期显示区域 -->
|
<!-- 日期显示区域 -->
|
||||||
<div class="date-section">
|
<div class="date-section">
|
||||||
@ -366,35 +344,40 @@ onMounted(() => {
|
|||||||
<!-- 详细信息 -->
|
<!-- 详细信息 -->
|
||||||
<div class="drama-details">
|
<div class="drama-details">
|
||||||
<div class="detail-icons">
|
<div class="detail-icons">
|
||||||
<img src="./images/剧场名icon.svg" alt="剧场名" class="detail-icon" />
|
<div class="detail-icon"></div>
|
||||||
<img src="./images/承制icon.svg" alt="承制" class="detail-icon" />
|
<div class="detail-icon"></div>
|
||||||
<img src="./images/版权icon.svg" alt="版权" class="detail-icon" />
|
<div class="detail-icon"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-text">
|
<div class="detail-text">
|
||||||
<p>剧场名:{{ item.series_author || '爱微剧场' }}</p>
|
<p>剧场名:爱微剧场</p>
|
||||||
<p>承制:{{ item.Manufacturing_Field || '妙想制片厂' }}</p>
|
<p>承制:妙想制片厂</p>
|
||||||
<p>版权:{{ item.Copyright_field || '可梦' }}</p>
|
<p>版权:可梦</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 数据统计 -->
|
<!-- 数据统计 -->
|
||||||
<div class="stats-row">
|
<div class="stats-row">
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<img src="./images/播放icon.svg" alt="播放" class="stat-icon" />
|
<div class="stat-icon play-icon"></div>
|
||||||
<span class="stat-value">{{ formatPlayCount(item.play_vv) || '9999W' }}</span>
|
<span class="stat-value">{{ formatPlayCount(item.play_vv) || '9999W' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<img src="./images/点赞icon.svg" alt="点赞" class="stat-icon" />
|
<div class="stat-icon like-icon"></div>
|
||||||
<span class="stat-value">{{ item.total_likes_formatted || '0' }}</span>
|
<span class="stat-value">374W</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 增长数据 -->
|
<!-- 增长数据 -->
|
||||||
<div class="growth-section">
|
<div class="growth-section">
|
||||||
<img src="./images/热度icon.svg" alt="热度" class="growth-icon" />
|
<div class="growth-icon"></div>
|
||||||
<span class="growth-value">{{ formatGrowth(item) || '300W' }}</span>
|
<span class="growth-value">{{ formatGrowth(item) || '300W' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 用户评论总结 -->
|
||||||
|
<div class="comment-summary">
|
||||||
|
<p>用户评论总结</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 空状态 -->
|
<!-- 空状态 -->
|
||||||
@ -426,8 +409,6 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -441,7 +422,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
/* 主容器 */
|
/* 主容器 */
|
||||||
.main-container {
|
.main-container {
|
||||||
max-width: 428px;
|
max-width: 375px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background: #ebedf2;
|
background: #ebedf2;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@ -450,7 +431,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
/* 顶部标题区域 */
|
/* 顶部标题区域 */
|
||||||
.header-section {
|
.header-section {
|
||||||
padding-top: 20px;
|
padding: 20px 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,12 +440,16 @@ onMounted(() => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
.logo-icon {
|
|
||||||
width: 40px;
|
.title-icon-left,
|
||||||
height: 40px;
|
.title-icon-right {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: #333;
|
||||||
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@ -473,25 +458,6 @@ onMounted(() => {
|
|||||||
font-family: Alatsi, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', SimHei, Arial, Helvetica, sans-serif;
|
font-family: Alatsi, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', SimHei, Arial, Helvetica, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-btn {
|
|
||||||
position: absolute;
|
|
||||||
right: 20px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
background: #4a90e2;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
font-size: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-btn:hover {
|
|
||||||
background: #357abd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 横幅区域 */
|
/* 横幅区域 */
|
||||||
.banner-section {
|
.banner-section {
|
||||||
margin: 20px 16px;
|
margin: 20px 16px;
|
||||||
@ -622,29 +588,28 @@ onMounted(() => {
|
|||||||
.ranking-list {
|
.ranking-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: white;
|
gap: 12px;
|
||||||
border-radius: 12px;
|
|
||||||
padding: 0 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ranking-item {
|
.ranking-item {
|
||||||
min-height: 120px;
|
background: white;
|
||||||
padding: 12px 0 ;
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
position: relative;
|
position: relative;
|
||||||
border-bottom: 1px solid #E1E3E5;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 排名徽章 */
|
/* 排名徽章 */
|
||||||
.rank-badge {
|
.rank-badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 11px;
|
top: -8px;
|
||||||
left: -1px;
|
left: -8px;
|
||||||
width: 24px;
|
width: 32px;
|
||||||
height: 28px;
|
height: 32px;
|
||||||
border-radius: 8px;
|
border-radius: 50%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -677,8 +642,8 @@ onMounted(() => {
|
|||||||
|
|
||||||
/* 海报容器 */
|
/* 海报容器 */
|
||||||
.poster-container {
|
.poster-container {
|
||||||
width: 84px;
|
width: 60px;
|
||||||
height: 112px;
|
height: 80px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@ -720,10 +685,10 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.detail-icon {
|
.detail-icon {
|
||||||
margin: 1px 0;
|
width: 4px;
|
||||||
width: 14px;
|
height: 4px;
|
||||||
height: 14px;
|
background: #9ca3af;
|
||||||
flex-shrink: 0;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-text {
|
.detail-text {
|
||||||
@ -756,6 +721,14 @@ onMounted(() => {
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.play-icon {
|
||||||
|
background: #4a90e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.like-icon {
|
||||||
|
background: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
.stat-value {
|
.stat-value {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #374151;
|
color: #374151;
|
||||||
@ -765,8 +738,13 @@ onMounted(() => {
|
|||||||
/* 增长数据 */
|
/* 增长数据 */
|
||||||
.growth-section {
|
.growth-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
background: #ef4444;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: white;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
min-width: 60px;
|
min-width: 60px;
|
||||||
}
|
}
|
||||||
@ -779,11 +757,17 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.growth-value {
|
.growth-value {
|
||||||
color: #ef4444;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 用户评论总结 */
|
||||||
|
.comment-summary {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.comment-summary p {
|
.comment-summary p {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
|
|||||||
@ -1,123 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const navigateToHome = () => {
|
|
||||||
router.push('/')
|
|
||||||
}
|
|
||||||
|
|
||||||
const navigateToAdmin = () => {
|
|
||||||
router.push('/admin')
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="root-app">
|
|
||||||
<!-- 导航栏 -->
|
|
||||||
<nav class="navigation">
|
|
||||||
<div class="nav-container">
|
|
||||||
<div class="nav-brand">
|
|
||||||
<h1>AI棒榜系统</h1>
|
|
||||||
</div>
|
|
||||||
<div class="nav-links">
|
|
||||||
<button
|
|
||||||
class="nav-btn"
|
|
||||||
:class="{ active: route.path === '/admin' }"
|
|
||||||
@click="navigateToAdmin"
|
|
||||||
>
|
|
||||||
后台管理
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- 路由视图 -->
|
|
||||||
<main class="main-content">
|
|
||||||
<router-view />
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.root-app {
|
|
||||||
min-height: 100vh;
|
|
||||||
background: #ebedf2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 导航栏样式 */
|
|
||||||
.navigation {
|
|
||||||
background: white;
|
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-container {
|
|
||||||
max-width: 375px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 12px 16px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-brand h1 {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-links {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn {
|
|
||||||
padding: 8px 16px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
background: white;
|
|
||||||
color: #666;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn:hover {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-color: #4a90e2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn.active {
|
|
||||||
background: #4a90e2;
|
|
||||||
color: white;
|
|
||||||
border-color: #4a90e2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 主内容区域 */
|
|
||||||
.main-content {
|
|
||||||
min-height: calc(100vh - 60px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 响应式设计 */
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.nav-container {
|
|
||||||
max-width: 100%;
|
|
||||||
padding: 10px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-brand h1 {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn {
|
|
||||||
padding: 6px 12px;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
<svg width="23" height="22" viewBox="0 0 23 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 7.6475C0 8.16531 0 14.2805 0 14.2805C0 16.59 1.5961 18.2743 2.9357 19.2803C4.49022 20.4491 6.05075 21.0036 6.34432 21.0597C6.6374 21.0036 8.19793 20.4491 9.75294 19.2803C11.092 18.2743 12.6881 16.59 12.6881 14.2805V13.7094C12.0797 13.3309 11.5066 12.8983 10.9758 12.4169C9.98786 13.0481 8.71463 13.012 7.76407 12.3257C7.56166 12.1719 7.51867 11.8848 7.66715 11.6785C7.81563 11.4721 8.1015 11.4217 8.31164 11.5647C8.89103 11.9827 9.65433 12.0452 10.294 11.727C10.1828 11.6028 10.0761 11.4765 9.97387 11.3483C9.4759 10.7211 9.10268 10.0648 8.85671 9.38445L8.6007 8.44662L8.58267 8.34643L8.58067 8.33541C8.57466 8.29922 8.56899 8.26299 8.56363 8.22671L8.56163 8.21319L8.54661 8.10548L8.5451 8.08944C8.5401 8.05437 8.53608 8.0183 8.53258 7.98274L8.53157 7.97272C8.52026 7.86458 8.5119 7.75616 8.50653 7.64757C8.50653 7.63454 8.50552 7.62202 8.50502 7.60899L8.50302 7.53885L8.50101 7.48124C6.62838 7.62753 4.55635 7.57593 2.85755 7.33295C1.58909 7.1511 1.57376 7.10999 1.07139 6.96036C0.518555 6.79431 0 7.12969 0 7.6475ZM6.35453 15.4708H6.33449C5.40904 15.4719 4.60439 16.1058 4.38672 17.0053H8.3023C8.08447 16.1059 7.27991 15.4721 6.35453 15.4708H6.35453ZM21.0654 0.431909C19.7504 0.192045 18.9529 -0.000125216 15.781 2.63279e-08C12.6091 0.000125268 11.8126 0.236747 10.5555 0.450036C9.8584 0.572662 9.4375 1.21924 9.4375 2C9.4375 3.93591 9.4375 7.35551 9.4375 7.35551C9.4375 7.41963 9.439 7.48375 9.44151 7.54688L9.44301 7.58796C9.44602 7.64908 9.45002 7.7097 9.45503 7.76981L9.45854 7.80638C9.46305 7.85848 9.46856 7.91009 9.47507 7.96119L9.48008 8.00126C9.4871 8.05637 9.49561 8.11198 9.50513 8.16709L9.51515 8.2247C9.52763 8.29497 9.54166 8.36497 9.55723 8.43462L9.58328 8.54483L9.60332 8.62248C9.62771 8.71476 9.65478 8.80632 9.68448 8.89712C10.3478 10.9279 12.2014 12.3251 13.3987 13.0456L13.4072 13.0516C14.5645 13.748 15.5539 14.0911 15.7813 14.1347C16.0744 14.0786 17.6348 13.524 19.1905 12.3557C20.5291 11.3492 22.1251 9.66562 22.1251 7.35552C22.1251 7.35552 22.1248 2.85222 22.1249 2.00018C22.125 1.14813 21.6113 0.536591 21.0654 0.431909ZM1.47105 11.6714C1.62246 11.4615 1.91529 11.414 2.12531 11.5652C2.79752 12.0504 3.70497 12.0504 4.37717 11.5652C4.58732 11.4222 4.87319 11.4726 5.02166 11.679C5.17014 11.8853 5.12715 12.1724 4.92474 12.3262C3.9256 13.0473 2.57689 13.0473 1.57775 12.3262C1.3677 12.1749 1.31993 11.8821 1.47105 11.6719V11.6714ZM17.7388 9.01526H13.8232C14.0411 9.91465 14.8456 10.5485 15.771 10.5497H15.7911C16.7165 10.5487 17.5212 9.91475 17.7388 9.01526L17.7388 9.01526ZM17.2011 4.6227C18.2003 3.90213 19.5485 3.90213 20.5476 4.6227C20.691 4.7176 20.7709 4.88334 20.7559 5.05461C20.7409 5.22588 20.6334 5.37521 20.4757 5.44375C20.3181 5.51229 20.1355 5.48905 20.0001 5.38318C19.328 4.89819 18.4208 4.89819 17.7487 5.38318C17.613 5.4863 17.432 5.50766 17.276 5.43897C17.1201 5.37028 17.0136 5.22237 16.9981 5.05264C16.9825 4.88292 17.0602 4.71811 17.2011 4.6222L17.2011 4.6227ZM11.0141 4.6227C12.0133 3.90189 13.3618 3.90189 14.3611 4.6227C14.5044 4.7176 14.5844 4.88334 14.5694 5.05461C14.5544 5.22588 14.4468 5.37521 14.2892 5.44374C14.1315 5.51228 13.949 5.48904 13.8135 5.38318C13.1414 4.89819 12.2342 4.89819 11.5621 5.38318C11.4263 5.48256 11.2475 5.50148 11.0939 5.43274C10.9402 5.36401 10.8352 5.21817 10.8187 5.05063C10.8022 4.8831 10.8768 4.71958 11.0141 4.6222L11.0141 4.6227Z" fill="#AFB1B7"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.4 KiB |
@ -1,3 +0,0 @@
|
|||||||
<svg width="20" height="18" viewBox="0 0 20 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.0559 0H0.945609C0.423363 0 0 0.447715 0 1V17C0 17.552 0.423633 18 0.945609 18H19.0559C19.5764 18 20 17.552 20 17V1C19.9992 0.448095 19.5763 0.000888824 19.0544 0H19.0559ZM4.50451 16H1.8916V13.9968H4.51359V11.9968H1.8916V9.9968H4.51359V7.9968H1.8916V5.9968H4.51359V3.9968H1.8916V2H4.50451V16V16ZM18.1093 3.99519L16.0637 3.99519V5.99519L18.1093 5.99519V7.99519L16.0637 7.99519V9.99519L18.1093 9.99519V11.9952H16.0637V13.9952L18.1093 13.9952V15.9968H16.041V1.9968H18.1093V3.9952V3.99519Z" fill="#AFB1B7"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 659 B |
@ -1,3 +0,0 @@
|
|||||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 0C17.072 0 22 4.928 22 11C22 17.072 17.072 22 11 22C4.928 22 0 17.072 0 11C0 4.928 4.928 0 11 0V0ZM11 5.5C7.964 5.5 5.5 7.964 5.5 11C5.50341 13.4706 7.15302 15.636 9.53393 16.2952C11.9148 16.9545 14.4433 15.946 15.7169 13.8291L13.8304 12.6983C13.0668 13.97 11.5486 14.5759 10.1193 14.1795C8.68994 13.7831 7.70072 12.4817 7.7012 10.9985C7.70168 9.51517 8.69175 8.21446 10.1213 7.81898C11.5509 7.4235 13.0687 8.03044 13.8315 9.3026L15.7169 8.1696C14.7228 6.51286 12.9321 5.49941 11 5.5H11Z" fill="#AFB1B7"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 662 B |
@ -1,8 +1,4 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import './style.css'
|
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
|
||||||
|
|
||||||
const app = createApp(App)
|
createApp(App).mount('#app')
|
||||||
|
|
||||||
app.use(router).mount('#app')
|
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
|
||||||
import AdminPanel from '../AdminPanel.vue'
|
|
||||||
|
|
||||||
const routes = [
|
|
||||||
{
|
|
||||||
path: '/admin',
|
|
||||||
name: 'Admin',
|
|
||||||
component: AdminPanel
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHistory(),
|
|
||||||
routes
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
/* 全局样式重置 */
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
background-color: #ebedf2;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
|
||||||
width: 100%;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 通用按钮样式 */
|
|
||||||
button {
|
|
||||||
cursor: pointer;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
font-family: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 通用输入框样式 */
|
|
||||||
input, textarea, select {
|
|
||||||
font-family: inherit;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 滚动条样式 */
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
background: #f1f1f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background: #c1c1c1;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #a8a8a8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 响应式设计 */
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
body {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||