2288 lines
65 KiB
Vue
2288 lines
65 KiB
Vue
<template>
|
||
<div class="stats-dashboard-mobile">
|
||
<div class="dashboard-container">
|
||
<!-- 看板 Tab 内容 -->
|
||
<div v-if="activeTab === 'dashboard'" class="tab-content">
|
||
<section class="stats-overview">
|
||
<!-- 合并后的总视频数和空间总计卡片 -->
|
||
<div class="stat-card primary-card main-card">
|
||
<div class="stat-header">
|
||
<div class="header-left">
|
||
<span class="stat-meta">视频统计</span>
|
||
<div class="stat-value-container">
|
||
<div class="stat-value video-count">
|
||
{{ totalVideos }}
|
||
<span class="value-label">个</span>
|
||
</div>
|
||
<div class="stat-value size-count">
|
||
{{ fileSizeTotal }}G
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="stat-subitems">
|
||
<div class="subitem" :title="`我喜欢的视频数: ${favoriteCount} (占用: ${favoriteSize}G)`">
|
||
<div class="subitem-icon like-icon">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>
|
||
</svg>
|
||
</div>
|
||
<span class="subitem-meta">我喜欢的 <span class="subitem-count">({{ favoriteCount }})</span></span>
|
||
<span class="subitem-size">{{ favoriteSize }}G</span>
|
||
</div>
|
||
<div class="subitem" :title="`我收藏的视频数: ${collectCount} (占用: ${collectSize}G)`">
|
||
<div class="subitem-icon collect-icon">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
|
||
</svg>
|
||
</div>
|
||
<span class="subitem-meta">我收藏的 <span class="subitem-count">({{ collectCount }})</span></span>
|
||
<span class="subitem-size">{{ collectSize }}G</span>
|
||
</div>
|
||
<div class="subitem" :title="`我关注的视频数: ${followCount} (占用: ${followSize}G)`">
|
||
<div class="subitem-icon follow-icon">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
|
||
<circle cx="8.5" cy="7" r="4"></circle>
|
||
<line x1="20" y1="8" x2="20" y2="14"></line>
|
||
<line x1="23" y1="11" x2="17" y2="11"></line>
|
||
</svg>
|
||
</div>
|
||
<span class="subitem-meta">我关注的 <span class="subitem-count">({{ followCount }})</span></span>
|
||
<span class="subitem-size">{{ followSize }}G</span>
|
||
</div>
|
||
<div class="subitem" :title="`图文视频数: ${graphicVideoCount} (占用: ${graphicVideoSize}G)`">
|
||
<div class="subitem-icon graphic-icon">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
||
<polyline points="21 15 16 10 5 21"></polyline>
|
||
</svg>
|
||
</div>
|
||
<span class="subitem-meta">图文视频 <span class="subitem-count">({{ graphicVideoCount }})</span></span>
|
||
<span class="subitem-size">{{ graphicVideoSize }}G</span>
|
||
</div>
|
||
|
||
<!-- 新增:合集视频统计项 -->
|
||
<div class="subitem" :title="`合集视频数: ${mixCount || 0} (占用: ${videoMixSize}G)`">
|
||
<div class="subitem-icon mix-icon">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path>
|
||
</svg>
|
||
</div>
|
||
<span class="subitem-meta">合集视频 <span class="subitem-count">({{ mixCount || 0 }})</span></span>
|
||
<span class="subitem-size">{{ videoMixSize }}G</span>
|
||
</div>
|
||
<!-- 新增:短剧视频统计项 -->
|
||
<div class="subitem" :title="`短剧视频数: ${seriesCount || 0} (占用: ${videoSeriesSize}G)`">
|
||
<div class="subitem-icon series-icon">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"></rect>
|
||
<line x1="7" y1="2" x2="7" y2="22"></line>
|
||
<line x1="17" y1="2" x2="17" y2="22"></line>
|
||
<line x1="2" y1="12" x2="22" y2="12"></line>
|
||
<line x1="2" y1="7" x2="7" y2="7"></line>
|
||
<line x1="2" y1="17" x2="7" y2="17"></line>
|
||
<line x1="17" y1="17" x2="22" y2="17"></line>
|
||
<line x1="17" y1="7" x2="22" y2="7"></line>
|
||
</svg>
|
||
</div>
|
||
<span class="subitem-meta">短剧视频 <span class="subitem-count">({{ seriesCount || 0 }})</span></span>
|
||
<span class="subitem-size">{{ videoSeriesSize }}G</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 最新同步视频列表 -->
|
||
<div class="stat-card secondary-card main-card">
|
||
<div class="stat-header">
|
||
<div class="header-left">
|
||
<span class="stat-meta">最新同步</span>
|
||
</div>
|
||
</div>
|
||
<div class="stat-subitems video-list-container">
|
||
<div v-for="(video, index) in topVideos" :key="index" class="subitem video-item" :title="video.title">
|
||
<div class="video-info">
|
||
<span class="subitem-meta video-title" @click="openVideoPlayer(video)">{{ video.title }}</span>
|
||
<span class="subitem-value video-time">{{ video.time }}</span>
|
||
</div>
|
||
</div>
|
||
<div v-if="topVideos.length === 0" class="empty-video-list">
|
||
暂无热门视频数据
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<!-- 日志 Tab 内容 -->
|
||
<div v-if="activeTab === 'log'" class="tab-content log-tab">
|
||
<div class="log-header">
|
||
<h2 class="log-title">系统日志</h2>
|
||
<div class="log-filter">
|
||
<button class="filter-btn" :class="{ active: logFilter === 'all' }" @click="logFilter = 'all'">
|
||
全部
|
||
</button>
|
||
<button class="filter-btn" :class="{ active: logFilter === 'debug' }" @click="logFilter = 'debug'">
|
||
DEBUG
|
||
</button>
|
||
<button class="filter-btn" :class="{ active: logFilter === 'error' }" @click="logFilter = 'error'">
|
||
ERROR
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="log-list">
|
||
<div v-for="(log,index) in filteredLogs" :key="index" class="log-item" :class="log.type.toLowerCase()" @click="openLogDetail(log)">
|
||
<div class="log-item-header">
|
||
<span class="log-type" :class="log.type.toLowerCase()">
|
||
{{ log.type }}
|
||
</span>
|
||
<span class="log-time">{{formatShowDate(log?.date) }}</span>
|
||
</div>
|
||
<div class="log-file">{{ log.fileName }}</div>
|
||
</div>
|
||
|
||
<div v-if="filteredLogs.length === 0" class="empty-log">
|
||
暂无{{ logFilter === 'all' ? '' : logFilter.toUpperCase() }}类型日志
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 新增:关注列表 Tab 内容(移动端专属重构) -->
|
||
<div v-if="activeTab === 'follow'" class="tab-content follow-tab">
|
||
<!-- 搜索+操作栏:移动端单行布局,适配小屏 -->
|
||
<div class="follow-header">
|
||
<!-- <a-select v-model:value="filterStatus" class="follow-filter-select" placeholder="筛选同步状态" @change="handleFilterChange">
|
||
<a-option value="all">全部</a-option>
|
||
<a-option value="openSync">已开启同步</a-option>
|
||
<a-option value="fullSync">全量同步</a-option>
|
||
<a-option value="closeSync">未开启同步</a-option>
|
||
</a-select> -->
|
||
<div class="search-wrapper">
|
||
<a-input v-model:value="quaryData.followUserName" placeholder="搜索博主名称或抖音号" allow-clear @pressEnter="handleSearch" @search="handleSearch" class="follow-search-input" />
|
||
</div>
|
||
<div class="follow-actions">
|
||
|
||
<a-button type="primary" class="follow-btn sync-btn" @click="handleSyncAll" :disabled="isSyncDisabled">
|
||
<SyncOutlined />
|
||
</a-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab导航:移动端横向滚动,适配多Tab -->
|
||
<div class="follow-tab-wrapper">
|
||
<div class="follow-tab-scroll">
|
||
<a-tabs v-model:value="activeFollowTabKey" type="line" class="follow-custom-tabs" @change="handleTabChange">
|
||
<a-tab-pane v-for="tab in tabList" :key="tab.key" :tab="`${tab.name}(${tab.total || 0})`" />
|
||
</a-tabs>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 关注列表:移动端单列卡片,滚动加载 -->
|
||
<div class="follow-list-container" @scroll="handleScroll">
|
||
<!-- 博主卡片:移动端专属布局【修复DOM结构】 -->
|
||
<a-card v-for="(item, index) in currentTabData" :key="item.id" :data-key="item.id" class="follow-card" :bordered="false" :hoverable="true" :class="{ 'no-followed-card': item.isNoFollowed }" style="margin: 0 !important; border-radius: 12px !important;">
|
||
<div class="follow-card-inner">
|
||
<!-- 顶部:头像+名称+操作【修复flex布局】 -->
|
||
<div class="follow-card-top">
|
||
<div class="avatar-wrapper">
|
||
<a-avatar shape="circle" size="large" :src="item.uperAvatar" v-if="item.uperAvatar" />
|
||
<a-avatar shape="circle" size="large" v-else class="avatar-placeholder">
|
||
{{ item.uperName?.charAt(0) || '#' }} <!-- 增加空值处理 -->
|
||
</a-avatar>
|
||
</div>
|
||
<div class="name-actions">
|
||
<div class="name-wrapper">
|
||
<h3 class="uper-name">{{ truncateText(item.uperName, 10) || '未知博主' }}<span style="font-size:12px;margin-left:5px;">{{item.douyinNo?`(${item.douyinNo})`:''}}</span></h3> <!-- 增加空值处理 -->
|
||
<span v-if="item.isNoFollowed" class="no-followed-badge">非关注</span>
|
||
|
||
</div>
|
||
<div class="top-actions">
|
||
<!-- 删除按钮:仅非关注显示 -->
|
||
<a-button type="text" class="delete-btn" v-if="item.isNoFollowed" @click="(e) => { e.stopPropagation(); handleDeleteItem(item); }" :disabled="item.isSaving" title="删除">
|
||
<DeleteOutlined />
|
||
</a-button>
|
||
<!-- 同步总开关 -->
|
||
<a-switch v-model:checked="item.openSync" @change="(checked) => handleSwitchChange(item, checked)" checked-children="开" un-checked-children="关" :disabled="item.isSaving" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 签名:移动端单行截断【增加强制显示】 -->
|
||
<div class="follow-card-desc" v-if="item.signature && item.signature.trim()">
|
||
<a-tooltip placement="top" :title="item.signature">
|
||
<span class="signature-text">{{ truncateText(item.signature, 25) }}</span>
|
||
</a-tooltip>
|
||
</div>
|
||
|
||
<!-- 路径+全量同步:仅同步开启时显示【修复布局】 -->
|
||
<div class="follow-card-bottom" v-if="item.openSync">
|
||
<div class="path-area">
|
||
<template v-if="item.isEditing">
|
||
<div class="edit-input-group">
|
||
<a-input v-model:value="item.savePath" placeholder="默认用博主名" @keypress.enter="() => handleSavePath(item)" maxlength="20" :disabled="item.isSaving" class="path-input" />
|
||
<a-button type="text" @click="() => handleSavePath(item)" :disabled="item.isSaving">
|
||
<SaveOutlined />
|
||
</a-button>
|
||
</div>
|
||
</template>
|
||
<template v-else>
|
||
<span class="path-text" :class="{ 'path-empty': !item.savePath }">
|
||
{{ item.savePath || '默认用博主名字' }}
|
||
</span>
|
||
<a-button type="text" @click="() => handleEditPath(item)" :disabled="item.isSaving" title="编辑保存路径" class="edit-btn">
|
||
<EditOutlined />
|
||
</a-button>
|
||
</template>
|
||
</div>
|
||
<div class="full-sync-area">
|
||
<span class="full-sync-label">全量同步</span>
|
||
<a-switch v-model:checked="item.fullSync" size="small" @change="(checked) => handleSyncChange(item, checked)" :disabled="item.isSaving" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</a-card>
|
||
|
||
<!-- 原有加载/空状态代码不变 -->
|
||
<div v-if="loading" class="loading-container">
|
||
<a-spin size="middle" />
|
||
<span class="loading-text">加载中...</span>
|
||
</div>
|
||
<div v-if="noMoreData && followData.length > 0" class="no-more-container">暂无更多</div>
|
||
<div v-if="followData.length === 0 && !loading" class="empty-container">
|
||
<Empty description="暂无关注博主" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 底部导航:新增【关注】Tab,改为三分栏 -->
|
||
<div class="bottom-nav">
|
||
<div class="nav-item" :class="{ active: activeTab === 'dashboard' }" @click="activeTab = 'dashboard'">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<polygon points="3 11 12 2 21 11 12 20 3 11"></polygon>
|
||
<line x1="12" y1="2" x2="12" y2="20"></line>
|
||
</svg>
|
||
<span>看板</span>
|
||
</div>
|
||
<div class="nav-item" :class="{ active: activeTab === 'follow' }" @click="handleSwitchToFollow">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
|
||
<circle cx="8.5" cy="7" r="4"></circle>
|
||
<line x1="20" y1="8" x2="20" y2="14"></line>
|
||
<line x1="23" y1="11" x2="17" y2="11"></line>
|
||
</svg>
|
||
<span>关注</span>
|
||
</div>
|
||
<div class="nav-item" :class="{ active: activeTab === 'log' }" @click="activeTab = 'log'">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
||
<polyline points="14 2 14 8 20 8"></polyline>
|
||
<line x1="16" y1="13" x2="8" y2="13"></line>
|
||
<line x1="16" y1="17" x2="8" y2="17"></line>
|
||
<polyline points="10 9 9 9 8 9"></polyline>
|
||
</svg>
|
||
<span>日志</span>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- 日志详情弹窗 -->
|
||
<div v-if="showLogModal" class="log-modal-mask" @click="closeLogModal">
|
||
<div class="log-modal-content" @click.stop>
|
||
<div class="log-modal-header">
|
||
<div class="header-left">
|
||
<span class="modal-type-tag" :class="currentLog?.type.toLowerCase()">
|
||
{{ currentLog?.type }}
|
||
</span>
|
||
<span class="modal-date">{{ formatShowDate(currentLog?.date) }}</span>
|
||
</div>
|
||
<button class="close-btn" @click="closeLogModal">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="log-modal-body">
|
||
<pre class="log-content">{{ logContent || '加载日志内容中...' }}</pre>
|
||
</div>
|
||
|
||
<div class="log-modal-footer">
|
||
<button class="copy-btn" @click="copyLogContent" :disabled="!logContent">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 6px;">
|
||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||
</svg>
|
||
复制
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 视频播放弹窗 -->
|
||
<div v-if="showVideoPlayer" class="video-modal-mask" @click="closeVideoPlayer">
|
||
<div class="video-modal-content" @click.stop>
|
||
<button class="video-close-btn floating-close-btn" @click="closeVideoPlayer">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||
</svg>
|
||
</button>
|
||
|
||
<div class="video-modal-body">
|
||
<div v-if="videoLoading" class="video-loading">
|
||
<div class="loading-spinner"></div>
|
||
<p>加载视频中...</p>
|
||
</div>
|
||
<div v-else-if="videoError" class="video-error">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#f44336" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="10"></circle>
|
||
<line x1="12" y1="8" x2="12" y2="12"></line>
|
||
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
||
</svg>
|
||
<p>视频加载失败</p>
|
||
<button class="retry-btn" @click="loadVideo(currentVideo)">重试</button>
|
||
</div>
|
||
<div v-else class="video-player-wrapper">
|
||
<video ref="videoPlayerRef" class="video-player" controls autoplay playsinline :src="videoPlayUrl" @error="handleVideoError" @pause="onVideoPause" @play="onVideoPlay" width="100%" height="100%" preload="auto" @loadedmetadata="handleVideoLoadedMetadata">
|
||
您的浏览器不支持HTML5视频播放
|
||
</video>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { ref, computed, onMounted, onUnmounted, UnwrapRef, reactive, nextTick, watch } from 'vue';
|
||
import { message, Spin, Empty, Tooltip, Modal, Form, FormInstance } from 'ant-design-vue';
|
||
import { useApiStore } from '@/store';
|
||
import {
|
||
CloseOutlined,
|
||
SearchOutlined,
|
||
PlusOutlined,
|
||
SyncOutlined,
|
||
SaveOutlined,
|
||
EditOutlined,
|
||
DeleteOutlined,
|
||
} from '@ant-design/icons-vue';
|
||
|
||
// ========== 原有仪表盘类型定义 ==========
|
||
interface Author {
|
||
name: string;
|
||
count: number;
|
||
icon: string;
|
||
}
|
||
interface Category {
|
||
name: string;
|
||
count: number;
|
||
color: string;
|
||
icon: string;
|
||
}
|
||
interface LogItem {
|
||
id: string;
|
||
type: 'DEBUG' | 'ERROR';
|
||
time: string;
|
||
message: string;
|
||
file: string;
|
||
date: string;
|
||
fileName: string;
|
||
}
|
||
interface TopVideoItem {
|
||
title: string;
|
||
time: string;
|
||
id: string;
|
||
}
|
||
|
||
// ========== 关注列表新增类型定义 ==========
|
||
interface TabItem {
|
||
key: string;
|
||
name: string;
|
||
total?: number;
|
||
}
|
||
interface FollowItem {
|
||
id: string;
|
||
mySelfId: string;
|
||
uperName: string;
|
||
enterprise: string;
|
||
signature: string;
|
||
uperAvatar: string;
|
||
fullSync: boolean;
|
||
openSync: boolean;
|
||
savePath?: string;
|
||
isEditing: boolean;
|
||
isSaving?: boolean;
|
||
uperId?: string;
|
||
douyinNo?: string;
|
||
isNoFollowed: boolean;
|
||
}
|
||
interface QuaryParam {
|
||
pageIndex: number;
|
||
pageSize: number;
|
||
followUserName: string | null;
|
||
mySelfId?: string;
|
||
}
|
||
// ========== 原有仪表盘状态 ==========
|
||
const totalVideos = ref<number>(0);
|
||
const fileSizeTotal = ref<string>('0.00');
|
||
const favoriteCount = ref<number>(0);
|
||
const collectCount = ref<number>(0);
|
||
const followCount = ref<number>(0);
|
||
const graphicVideoCount = ref<number>(0);
|
||
const mixCount = ref<number>(0);
|
||
const seriesCount = ref<number>(0);
|
||
const favoriteSize = ref<string>('0.00');
|
||
const collectSize = ref<string>('0.00');
|
||
const followSize = ref<string>('0.00');
|
||
const graphicVideoSize = ref<string>('0.00');
|
||
const videoMixSize = ref<string>('0.00');
|
||
const videoSeriesSize = ref<string>('0.00');
|
||
const activeTab = ref<string>('dashboard');
|
||
const logFilter = ref<string>('all');
|
||
const logs = ref<LogItem[]>([]);
|
||
const showLogModal = ref<boolean>(false);
|
||
const currentLog = ref<LogItem | null>(null);
|
||
const logContent = ref<string>('');
|
||
const topVideos = ref<TopVideoItem[]>([]);
|
||
const showVideoPlayer = ref<boolean>(false);
|
||
const currentVideo = ref<TopVideoItem | null>(null);
|
||
const videoPlayUrl = ref<string>('');
|
||
const videoLoading = ref<boolean>(false);
|
||
const videoError = ref<boolean>(false);
|
||
const videoPlayerRef = ref<HTMLVideoElement | null>(null);
|
||
const isVideoPaused = ref<boolean>(false);
|
||
const filteredLogs = computed(() => {
|
||
if (logFilter.value === 'all') return logs.value;
|
||
return logs.value.filter((log) => log.type.toLowerCase() === logFilter.value);
|
||
});
|
||
|
||
// ========== 关注列表状态 ==========
|
||
const tabList = ref<TabItem[]>([]);
|
||
const activeFollowTabKey = ref('');
|
||
const followData = ref<FollowItem[]>([]);
|
||
const loading = ref(false);
|
||
const noMoreData = ref(false);
|
||
const hasMore = ref(true);
|
||
const isSyncDisabled = ref(false);
|
||
const isAddDisabled = ref(false);
|
||
|
||
// 搜索参数
|
||
const quaryData: UnwrapRef<QuaryParam> = reactive({
|
||
pageIndex: 0,
|
||
pageSize: 10, // 移动端每页加载10条,更友好
|
||
followUserName: null,
|
||
mySelfId: '',
|
||
});
|
||
// 计算当前Tab数据
|
||
const currentTabData = computed(() => {
|
||
return followData.value.filter((item) => item.mySelfId === activeFollowTabKey.value);
|
||
});
|
||
|
||
// 组件名称
|
||
defineOptions({ name: 'StatsDashboardMobile' });
|
||
|
||
// ========== 生命周期 ==========
|
||
onMounted(() => {
|
||
// 加载仪表盘数据
|
||
loadDashboardData();
|
||
loadLogData();
|
||
TopVideo();
|
||
// 初始化关注列表参数
|
||
quaryData.pageIndex = 0;
|
||
// 监听滚动(关注列表)
|
||
const followContainer = document.querySelector('.follow-list-container');
|
||
if (followContainer) {
|
||
followContainer.addEventListener('scroll', handleScroll);
|
||
}
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
// 移除关注列表滚动监听
|
||
const followContainer = document.querySelector('.follow-list-container');
|
||
if (followContainer) {
|
||
followContainer.removeEventListener('scroll', handleScroll);
|
||
}
|
||
});
|
||
|
||
// 监听关注Tab切换,初始化数据
|
||
watch(
|
||
() => activeTab.value,
|
||
(newVal) => {
|
||
if (newVal === 'follow' && tabList.value.length === 0) {
|
||
GetCookies().then(() => {
|
||
if (activeFollowTabKey.value) {
|
||
quaryData.mySelfId = activeFollowTabKey.value;
|
||
GetFollows(true);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
);
|
||
|
||
const loadDashboardData = async () => {
|
||
try {
|
||
const res = await useApiStore().VideoStatics();
|
||
totalVideos.value = res.data.videoCount;
|
||
fileSizeTotal.value = res.data.videoSizeTotal || '0.00';
|
||
|
||
favoriteCount.value = res.data.favoriteCount;
|
||
collectCount.value = res.data.collectCount;
|
||
followCount.value = res.data.followCount || 0;
|
||
graphicVideoCount.value = res.data.graphicVideoCount || 0;
|
||
mixCount.value = res.data.mixCount || 0;
|
||
seriesCount.value = res.data.seriesCount || 0;
|
||
|
||
favoriteSize.value = res.data.videoFavoriteSize || '0.00';
|
||
collectSize.value = res.data.videoCollectSize || '0.00';
|
||
followSize.value = res.data.videoFollowSize || '0.00';
|
||
graphicVideoSize.value = res.data.graphicVideoSize || '0.00';
|
||
videoMixSize.value = res.data.videoMixSize || '0.00';
|
||
videoSeriesSize.value = res.data.videoSeriesSize || '0.00';
|
||
} catch (err) {
|
||
console.error('加载仪表盘数据失败:', err);
|
||
}
|
||
};
|
||
const loadLogData = async () => {
|
||
try {
|
||
useApiStore()
|
||
.MobileLogs()
|
||
.then((res) => {
|
||
if (res.code == 0) logs.value = res.data;
|
||
});
|
||
} catch (err) {
|
||
console.error('加载日志数据失败:', err);
|
||
}
|
||
};
|
||
const TopVideo = () => {
|
||
useApiStore()
|
||
.TopVideo(10)
|
||
.then((res) => {
|
||
if (res.code == 0) topVideos.value = res.data;
|
||
});
|
||
};
|
||
const openLogDetail = (log: LogItem) => {
|
||
currentLog.value = log;
|
||
showLogModal.value = true;
|
||
loadLogDetailContent(log);
|
||
};
|
||
const closeLogModal = () => {
|
||
showLogModal.value = false;
|
||
currentLog.value = null;
|
||
logContent.value = '';
|
||
};
|
||
const loadLogDetailContent = (log: LogItem) => {
|
||
try {
|
||
const requestParams = `${log.type.toLowerCase()}/${log.date}`;
|
||
useApiStore()
|
||
.apiGetLogs(requestParams)
|
||
.then((logContentStr: string) => {
|
||
const formattedLog = formatLogTime(logContentStr);
|
||
logContent.value = formattedLog.split('\n').reverse().join('\n');
|
||
});
|
||
} catch (err) {
|
||
logContent.value = '日志内容加载失败,请重试';
|
||
}
|
||
};
|
||
const formatShowDate = (dateStr?: string) => {
|
||
if (!dateStr || dateStr.length !== 8) return dateStr || '';
|
||
return `${dateStr.slice(0, 4)}-${dateStr.slice(4, 6)}-${dateStr.slice(6, 8)}`;
|
||
};
|
||
const formatLogTime = (logContent: string) => {
|
||
const timeRegex = /\d{4}-\d{2}-\d{2} (\d{2}:\d{2}:\d{2})\.\d+ \+08:00/g;
|
||
return logContent.replace(timeRegex, '$1');
|
||
};
|
||
const copyLogContent = () => {
|
||
if (!logContent.value) return message.warn('暂无日志内容可复制');
|
||
if (navigator.clipboard) {
|
||
navigator.clipboard
|
||
.writeText(logContent.value)
|
||
.then(() => message.success('复制成功'))
|
||
.catch(() => fallbackCopyTextToClipboard(logContent.value));
|
||
} else {
|
||
fallbackCopyTextToClipboard(logContent.value);
|
||
}
|
||
};
|
||
const fallbackCopyTextToClipboard = (text: string) => {
|
||
const textArea = document.createElement('textarea');
|
||
textArea.value = text;
|
||
textArea.style.position = 'fixed';
|
||
textArea.style.opacity = '0';
|
||
document.body.appendChild(textArea);
|
||
textArea.select();
|
||
try {
|
||
document.execCommand('copy') ? message.success('复制成功') : message.error('复制失败');
|
||
} catch (err) {
|
||
message.error('复制失败,请手动复制');
|
||
}
|
||
document.body.removeChild(textArea);
|
||
};
|
||
const openVideoPlayer = (video: TopVideoItem) => {
|
||
if (!video.id) return message.warn('视频ID不存在');
|
||
currentVideo.value = video;
|
||
showVideoPlayer.value = true;
|
||
loadVideo(video).then(() => {
|
||
videoPlayerRef.value?.play().catch((err) => console.log('自动播放被拦截:', err));
|
||
});
|
||
};
|
||
const loadVideo = async (video: TopVideoItem) => {
|
||
try {
|
||
videoLoading.value = true;
|
||
videoError.value = false;
|
||
const timestamp = new Date().getTime();
|
||
videoPlayUrl.value = `${import.meta.env.VITE_API_URL}api/Video/play/${video.id}?t=${timestamp}`;
|
||
} catch (err) {
|
||
videoError.value = true;
|
||
message.error('视频加载失败');
|
||
} finally {
|
||
videoLoading.value = false;
|
||
}
|
||
};
|
||
const closeVideoPlayer = () => {
|
||
showVideoPlayer.value = false;
|
||
currentVideo.value = null;
|
||
videoPlayUrl.value = '';
|
||
videoLoading.value = false;
|
||
videoError.value = false;
|
||
isVideoPaused.value = false;
|
||
videoPlayerRef.value?.pause();
|
||
};
|
||
const handleVideoError = () => {
|
||
videoError.value = true;
|
||
};
|
||
const onVideoPause = () => {
|
||
isVideoPaused.value = true;
|
||
};
|
||
const onVideoPlay = () => {
|
||
isVideoPaused.value = false;
|
||
};
|
||
const deleteCurrentVideo = async () => {
|
||
closeVideoPlayer();
|
||
TopVideo();
|
||
loadDashboardData();
|
||
};
|
||
const handleVideoLoadedMetadata = () => {
|
||
if (!videoPlayerRef.value) return;
|
||
const video = videoPlayerRef.value;
|
||
const wrapper = document.querySelector('.video-player-wrapper');
|
||
if (!wrapper) return;
|
||
const videoRatio = video.videoWidth / video.videoHeight;
|
||
if (videoRatio <= 1) {
|
||
video.style.width = '100%';
|
||
video.style.height = '100%';
|
||
video.style.objectFit = 'cover';
|
||
video.style.position = 'static';
|
||
} else {
|
||
video.style.objectFit = 'contain';
|
||
video.style.position = 'absolute';
|
||
video.style.top = '50%';
|
||
video.style.left = '50%';
|
||
video.style.transform = 'translate(-50%, -50%)';
|
||
video.style.maxHeight = '100%';
|
||
video.style.maxWidth = '100%';
|
||
}
|
||
};
|
||
|
||
// ========== 关注列表核心方法 ==========
|
||
// 切换到关注Tab时初始化
|
||
const handleSwitchToFollow = () => {
|
||
activeTab.value = 'follow';
|
||
nextTick(() => {
|
||
if (tabList.value.length === 0) {
|
||
GetCookies();
|
||
}
|
||
});
|
||
};
|
||
// 获取Cookie/Tab列表
|
||
const GetCookies = (): Promise<void> => {
|
||
return new Promise((resolve) => {
|
||
useApiStore()
|
||
.CookieList()
|
||
.then((res) => {
|
||
if (res.code === 0) {
|
||
tabList.value = res.data;
|
||
if (tabList.value.length > 0 && !activeFollowTabKey.value) {
|
||
activeFollowTabKey.value = tabList.value[0].key;
|
||
}
|
||
}
|
||
resolve();
|
||
})
|
||
.catch((err) => {
|
||
console.error('获取Tab数据失败:', err);
|
||
message.error('获取Tab数据失败,请刷新重试');
|
||
resolve();
|
||
});
|
||
});
|
||
};
|
||
// 获取关注列表
|
||
const GetFollows = (isReset = false) => {
|
||
if (loading.value || (noMoreData.value && !isReset)) return;
|
||
loading.value = true;
|
||
useApiStore()
|
||
.FollowList(quaryData)
|
||
.then((res) => {
|
||
if (res.code === 0) {
|
||
const newData = res.data.data || [];
|
||
const total = res.data.total || 0;
|
||
// 格式化数据
|
||
const formattedData = newData.map((item) => ({
|
||
...item,
|
||
isSaving: false,
|
||
isEditing: item.isEditing ?? false,
|
||
uperId: item.uperId || item.id,
|
||
isNoFollowed: item.isNoFollowed ?? false,
|
||
}));
|
||
// 更新Tab总数
|
||
if (isReset) {
|
||
const tabIndex = tabList.value.findIndex((tab) => tab.key === activeFollowTabKey.value);
|
||
if (tabIndex !== -1) tabList.value[tabIndex].total = total;
|
||
}
|
||
// 判断是否有更多
|
||
if (formattedData.length < quaryData.pageSize) {
|
||
noMoreData.value = true;
|
||
hasMore.value = false;
|
||
} else {
|
||
noMoreData.value = false;
|
||
hasMore.value = true;
|
||
}
|
||
// 合并数据
|
||
if (isReset) {
|
||
followData.value = formattedData;
|
||
} else {
|
||
const existingKeys = followData.value.map((item) => item.id);
|
||
const uniqueNewData = formattedData.filter((item) => !existingKeys.includes(item.id));
|
||
followData.value = [...followData.value, ...uniqueNewData];
|
||
}
|
||
quaryData.pageIndex += 1;
|
||
} else {
|
||
message.error('获取关注列表失败');
|
||
noMoreData.value = true;
|
||
hasMore.value = false;
|
||
}
|
||
})
|
||
.catch((err) => {
|
||
console.error('获取关注列表异常:', err);
|
||
message.error('网络异常,请重试');
|
||
noMoreData.value = true;
|
||
hasMore.value = false;
|
||
})
|
||
.finally(() => {
|
||
loading.value = false;
|
||
});
|
||
};
|
||
// 清空搜索
|
||
const clearSearch = () => {
|
||
quaryData.followUserName = null;
|
||
quaryData.pageIndex = 0;
|
||
GetFollows(true);
|
||
};
|
||
// 执行搜索
|
||
const handleSearch = () => {
|
||
quaryData.pageIndex = 0;
|
||
noMoreData.value = false;
|
||
hasMore.value = true;
|
||
GetFollows(true);
|
||
};
|
||
// Tab切换
|
||
const handleTabChange = (key: string) => {
|
||
if (activeFollowTabKey.value === key) return;
|
||
activeFollowTabKey.value = key;
|
||
quaryData.mySelfId = key;
|
||
quaryData.pageIndex = 0;
|
||
noMoreData.value = false;
|
||
hasMore.value = true;
|
||
quaryData.followUserName = null;
|
||
GetFollows(true);
|
||
};
|
||
// 滚动加载更多
|
||
const handleScroll = () => {
|
||
const container = document.querySelector('.follow-list-container');
|
||
if (!container || loading.value || !hasMore.value || noMoreData.value) return;
|
||
const { scrollTop, scrollHeight, clientHeight } = container as HTMLDivElement;
|
||
if (scrollTop + clientHeight >= scrollHeight - 50) {
|
||
// 移动端提前50px加载
|
||
GetFollows(false);
|
||
}
|
||
};
|
||
// 同步总开关变更
|
||
const handleSwitchChange = (item: FollowItem, checked: boolean) => {
|
||
item.openSync = checked;
|
||
uploadSyncStatus(item);
|
||
};
|
||
// 全量同步开关变更
|
||
const handleSyncChange = (item: FollowItem, checked: boolean) => {
|
||
item.fullSync = checked;
|
||
uploadSyncStatus(item);
|
||
};
|
||
// 编辑路径
|
||
const handleEditPath = (item: FollowItem) => {
|
||
if (item.isSaving) return;
|
||
item.isEditing = true;
|
||
nextTick(() => {
|
||
const input = document.querySelector(`.follow-card[data-key="${item.id}"] .path-input`) as HTMLInputElement | null;
|
||
input?.focus();
|
||
});
|
||
};
|
||
// 保存路径
|
||
const handleSavePath = (item: FollowItem) => {
|
||
if (item.isSaving) return;
|
||
uploadSyncStatus(item);
|
||
};
|
||
// 更新同步状态
|
||
const uploadSyncStatus = (item: FollowItem) => {
|
||
item.isSaving = true;
|
||
useApiStore()
|
||
.OpenOrCloseSync({
|
||
Id: item.id,
|
||
OpenSync: item.openSync,
|
||
FullSync: item.fullSync,
|
||
SavePath: item.savePath,
|
||
uperId: item.uperId,
|
||
})
|
||
.then((res) => {
|
||
if (res.code === 0) {
|
||
message.success(`保存成功,下次任务生效`);
|
||
item.isEditing = false;
|
||
} else {
|
||
message.error('保存失败' + (res.message || '未知错误'));
|
||
}
|
||
})
|
||
.catch((err) => {
|
||
console.error('保存失败', err);
|
||
message.error('保存失败,请重试');
|
||
})
|
||
.finally(() => {
|
||
item.isSaving = false;
|
||
});
|
||
};
|
||
// 批量同步
|
||
const handleSyncAll = () => {
|
||
if (isSyncDisabled.value) return;
|
||
isSyncDisabled.value = true;
|
||
loading.value = true;
|
||
useApiStore()
|
||
.StartJobNow()
|
||
.then((res) => {
|
||
if (res.code === 0) {
|
||
message.success('后台开始同步,请注意查收');
|
||
} else {
|
||
message.error('同步失败:' + (res.message || '未知错误'));
|
||
}
|
||
})
|
||
.catch((err) => {
|
||
console.error('同步异常:', err);
|
||
message.error('同步失败,请重试');
|
||
})
|
||
.finally(() => {
|
||
isSyncDisabled.value = false;
|
||
loading.value = false;
|
||
});
|
||
};
|
||
// 文本截断
|
||
const truncateText = (text: string, maxLength: number): string => {
|
||
if (!text || text.length <= maxLength) return text;
|
||
return text.slice(0, maxLength) + '...';
|
||
};
|
||
|
||
// 删除关注博主
|
||
const handleDeleteItem = (item: FollowItem) => {
|
||
Modal.confirm({
|
||
title: '确认删除',
|
||
content: `确定删除「${item.uperName}」吗?删除后无法恢复`,
|
||
okText: '删除',
|
||
cancelText: '取消',
|
||
okType: 'danger',
|
||
maskClosable: false,
|
||
onOk: () => {
|
||
return new Promise((resolve, reject) => {
|
||
useApiStore()
|
||
.DelFollow({
|
||
id: item.id,
|
||
mySelfId: item.mySelfId,
|
||
uperId: item.uperId,
|
||
})
|
||
.then((res) => {
|
||
if (res.code === 0) {
|
||
message.success('删除成功!');
|
||
// 重新加载数据
|
||
quaryData.pageIndex = 0;
|
||
GetFollows(true);
|
||
// 更新Tab总数
|
||
const tabIndex = tabList.value.findIndex((tab) => tab.key === activeFollowTabKey.value);
|
||
if (tabIndex !== -1) {
|
||
tabList.value[tabIndex].total = (tabList.value[tabIndex].total || 1) - 1;
|
||
}
|
||
resolve(true);
|
||
} else {
|
||
message.error('删除失败:' + (res.message || '未知错误'));
|
||
reject(false);
|
||
}
|
||
})
|
||
.catch((err) => {
|
||
console.error('删除异常:', err);
|
||
message.error('网络异常,请重试');
|
||
reject(false);
|
||
});
|
||
});
|
||
},
|
||
});
|
||
};
|
||
</script>
|
||
|
||
<style>
|
||
html,
|
||
body {
|
||
margin: 0;
|
||
padding: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
overflow-x: hidden;
|
||
}
|
||
#app {
|
||
margin: 0;
|
||
padding: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
.ant-message {
|
||
z-index: 10000 !important;
|
||
}
|
||
/* 强制清除antd卡片默认边距和内边距 */
|
||
.ant-card {
|
||
margin: 0 !important;
|
||
border-radius: 12px !important;
|
||
}
|
||
.ant-card-body {
|
||
padding: 0 !important;
|
||
}
|
||
|
||
/* ========== 移动端隐藏所有滚动条 - 核心代码 ========== */
|
||
/* 针对webkit内核浏览器(iOS/安卓Chrome/Edge等) */
|
||
*::-webkit-scrollbar {
|
||
width: 0px !important; /* 垂直滚动条宽度 */
|
||
height: 0px !important; /* 水平滚动条高度 */
|
||
display: none !important; /* 直接隐藏滚动条元素 */
|
||
}
|
||
/* 滚动条轨道/滑块也隐藏,防止残留 */
|
||
*::-webkit-scrollbar-track,
|
||
*::-webkit-scrollbar-thumb {
|
||
background: transparent !important;
|
||
border-radius: 0 !important;
|
||
}
|
||
/* 火狐浏览器适配(可选,移动端火狐占比极低) */
|
||
* {
|
||
scrollbar-width: none !important; /* 火狐隐藏滚动条 */
|
||
-ms-overflow-style: none !important; /* IE/Edge 隐藏滚动条 */
|
||
}
|
||
</style>
|
||
|
||
<style scoped>
|
||
/* ========== 原有仪表盘样式(保留,无需修改) ========== */
|
||
.stats-dashboard-mobile {
|
||
min-height: 100vh;
|
||
background-color: #ffffff;
|
||
color: #333333;
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
padding: 5px 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding-bottom: 80px;
|
||
position: relative;
|
||
}
|
||
.dashboard-container {
|
||
width: 100%;
|
||
padding: 0 5px;
|
||
box-sizing: border-box;
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
.tab-content {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
.stats-overview {
|
||
display: grid;
|
||
grid-template-columns: 1fr;
|
||
gap: 20px;
|
||
margin-bottom: 5px;
|
||
}
|
||
.main-card {
|
||
padding: 0px 10px;
|
||
border-radius: 12px;
|
||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
|
||
transition: all 0.3s ease;
|
||
position: relative;
|
||
overflow: hidden;
|
||
border: 1px solid #f0f0f0;
|
||
background: #fff;
|
||
}
|
||
.main-card:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 6px 25px rgba(0, 0, 0, 0.08);
|
||
}
|
||
.stat-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
margin-bottom: 16px;
|
||
}
|
||
.header-left {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
width: 100%;
|
||
}
|
||
.stat-meta {
|
||
font-size: 14px;
|
||
color: #666666;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.4px;
|
||
font-weight: 500;
|
||
}
|
||
.stat-value-container {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
width: 100%;
|
||
margin-top: 8px;
|
||
}
|
||
.stat-value {
|
||
font-size: 26px;
|
||
font-weight: 700;
|
||
color: #1a1a1a;
|
||
line-height: 1.1;
|
||
}
|
||
.value-label {
|
||
font-size: 14px;
|
||
color: #666;
|
||
font-weight: 500;
|
||
margin-left: 4px;
|
||
}
|
||
.video-count {
|
||
font-size: 22px;
|
||
}
|
||
.size-count {
|
||
font-size: 22px;
|
||
color: #666;
|
||
}
|
||
.unit {
|
||
font-size: 18px;
|
||
color: #444444;
|
||
font-weight: 500;
|
||
}
|
||
.stat-icon {
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
background-color: rgba(76, 175, 80, 0.15);
|
||
color: #4caf50;
|
||
}
|
||
.secondary-card .stat-icon {
|
||
background-color: rgba(33, 150, 243, 0.15);
|
||
color: #2196f3;
|
||
}
|
||
.stat-subitems {
|
||
display: grid;
|
||
grid-template-columns: 1fr;
|
||
gap: 12px;
|
||
padding-top: 16px;
|
||
border-top: 1px solid #f0f0f0;
|
||
}
|
||
.subitem {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 12px 14px;
|
||
background: #ffffff;
|
||
border: 1px solid #f0f0f0;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||
transition: all 0.2s ease;
|
||
cursor: default;
|
||
justify-content: space-between;
|
||
}
|
||
.subitem:hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.06);
|
||
border-color: #e0e0e0;
|
||
}
|
||
.subitem-icon {
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 6px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
background-color: rgba(233, 30, 99, 0.1);
|
||
color: #e91e63;
|
||
}
|
||
.collect-icon {
|
||
background-color: rgba(255, 152, 0, 0.1);
|
||
color: #ff9800;
|
||
}
|
||
.follow-icon {
|
||
background-color: rgba(156, 39, 176, 0.1);
|
||
color: #9c27b0;
|
||
}
|
||
.graphic-icon {
|
||
background-color: rgba(255, 159, 64, 0.1);
|
||
color: #d9091a;
|
||
}
|
||
.video-list-icon {
|
||
background-color: rgba(33, 150, 243, 0.15);
|
||
color: #2196f3;
|
||
}
|
||
.video-index {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
}
|
||
.subitem-meta {
|
||
font-size: 13px;
|
||
color: #666666;
|
||
font-weight: 500;
|
||
flex: 1;
|
||
}
|
||
.subitem-count {
|
||
color: #222;
|
||
font-weight: 600;
|
||
margin-left: 4px;
|
||
}
|
||
.subitem-size {
|
||
font-size: 12px;
|
||
color: #999;
|
||
font-weight: 400;
|
||
}
|
||
.video-title {
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
line-height: 1.4;
|
||
max-height: 2.8em;
|
||
width: 100%;
|
||
padding-right: 4px;
|
||
cursor: pointer;
|
||
transition: color 0.2s;
|
||
}
|
||
.video-title:hover {
|
||
color: #2196f3;
|
||
}
|
||
.video-time {
|
||
font-size: 11px !important;
|
||
color: #999;
|
||
margin-top: 2px;
|
||
font-weight: 300 !important;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
}
|
||
.video-info {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
}
|
||
.subitem-value {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
color: #222222;
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 3px;
|
||
}
|
||
.subitem-value .unit {
|
||
font-size: 12px;
|
||
color: #555555;
|
||
font-weight: 500;
|
||
}
|
||
.primary-card {
|
||
border-top: 3px solid #4caf50;
|
||
}
|
||
.secondary-card {
|
||
border-top: 3px solid #2196f3;
|
||
}
|
||
.empty-video-list {
|
||
text-align: center;
|
||
padding: 10px 0;
|
||
color: #999;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* 底部导航样式:改为三分栏 */
|
||
.bottom-nav {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 60px;
|
||
background-color: #ffffff;
|
||
border-top: 1px solid #f0f0f0;
|
||
display: flex;
|
||
justify-content: space-around;
|
||
align-items: center;
|
||
z-index: 100;
|
||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
|
||
}
|
||
.nav-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex: 1;
|
||
height: 100%;
|
||
color: #666666;
|
||
transition: all 0.2s ease;
|
||
cursor: pointer;
|
||
}
|
||
.nav-item.active {
|
||
color: #4caf50;
|
||
}
|
||
.nav-item span {
|
||
font-size: 12px;
|
||
margin-top: 4px;
|
||
font-weight: 500;
|
||
}
|
||
.nav-item svg {
|
||
transition: all 0.2s ease;
|
||
}
|
||
.nav-item.active svg {
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
/* 日志 Tab 样式 */
|
||
.log-tab {
|
||
padding: 5px 0;
|
||
}
|
||
.log-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
padding: 0 5px;
|
||
}
|
||
.log-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #1a1a1a;
|
||
margin: 0;
|
||
}
|
||
.log-filter {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
.filter-btn {
|
||
padding: 6px 12px;
|
||
border-radius: 20px;
|
||
border: 1px solid #e0e0e0;
|
||
background-color: #ffffff;
|
||
color: #666666;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
}
|
||
.filter-btn.active {
|
||
background-color: #4caf50;
|
||
color: #ffffff;
|
||
border-color: #4caf50;
|
||
}
|
||
.filter-btn:hover:not(.active) {
|
||
border-color: #cccccc;
|
||
color: #333333;
|
||
}
|
||
|
||
/* 日志列表样式 */
|
||
.log-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
.log-item {
|
||
padding: 10px;
|
||
border-radius: 5px;
|
||
border: 1px solid #f0f0f0;
|
||
background-color: #ffffff;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
}
|
||
.log-item:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||
}
|
||
.log-item.debug {
|
||
border-left: 4px solid #2196f3;
|
||
}
|
||
.log-item.error {
|
||
border-left: 4px solid #f44336;
|
||
}
|
||
.log-item-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
.log-type {
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
}
|
||
.log-type.debug {
|
||
background-color: rgba(33, 150, 243, 0.1);
|
||
color: #2196f3;
|
||
}
|
||
.log-type.error {
|
||
background-color: rgba(244, 67, 54, 0.1);
|
||
color: #f44336;
|
||
}
|
||
.log-time {
|
||
font-size: 11px;
|
||
color: #999999;
|
||
}
|
||
.log-file {
|
||
font-size: 12px;
|
||
color: #666666;
|
||
}
|
||
.empty-log {
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
color: #999999;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* 日志详情弹窗样式 */
|
||
.log-modal-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
padding: 10px;
|
||
box-sizing: border-box;
|
||
}
|
||
.log-modal-content {
|
||
width: 100%;
|
||
max-width: 500px;
|
||
background-color: #ffffff;
|
||
border-radius: 12px;
|
||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||
max-height: 80vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.log-modal-header {
|
||
padding: 5px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
.header-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.modal-type-tag {
|
||
padding: 3px 8px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
}
|
||
.modal-type-tag.debug {
|
||
background-color: rgba(33, 150, 243, 0.1);
|
||
color: #2196f3;
|
||
}
|
||
.modal-type-tag.error {
|
||
background-color: rgba(244, 67, 54, 0.1);
|
||
color: #f44336;
|
||
}
|
||
.modal-date {
|
||
font-size: 14px;
|
||
color: #666;
|
||
font-weight: 500;
|
||
}
|
||
.close-btn {
|
||
background: transparent;
|
||
border: none;
|
||
cursor: pointer;
|
||
color: #666;
|
||
padding: 4px;
|
||
border-radius: 4px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
.close-btn:hover {
|
||
background-color: #f5f5f5;
|
||
color: #333;
|
||
}
|
||
.log-modal-body {
|
||
padding: 10px;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.file-name {
|
||
font-size: 14px;
|
||
color: #333;
|
||
font-weight: 600;
|
||
margin-bottom: 5px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
.log-content {
|
||
flex: 1;
|
||
margin: 0;
|
||
padding: 5px;
|
||
background-color: #f9f9f9;
|
||
border-radius: 8px;
|
||
overflow-y: auto;
|
||
overflow-x: auto;
|
||
font-family: Consolas, Monaco, monospace;
|
||
font-size: 13px;
|
||
line-height: 1.6;
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
-webkit-overflow-scrolling: touch;
|
||
touch-action: pan-y;
|
||
color: #333;
|
||
}
|
||
.log-modal-footer {
|
||
padding: 5px 20px;
|
||
border-top: 1px solid #f0f0f0;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
.copy-btn {
|
||
padding: 8px 16px;
|
||
background-color: #4caf50;
|
||
color: #ffffff;
|
||
border: none;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
transition: all 0.2s ease;
|
||
}
|
||
.copy-btn:disabled {
|
||
background-color: #cccccc;
|
||
cursor: not-allowed;
|
||
}
|
||
.copy-btn:hover:not(:disabled) {
|
||
background-color: #43a047;
|
||
}
|
||
/* 视频弹窗样式 */
|
||
.video-modal-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: #000;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 2000;
|
||
padding: 0;
|
||
margin: 0;
|
||
box-sizing: border-box;
|
||
overflow: hidden;
|
||
}
|
||
.video-modal-content {
|
||
width: 100vw;
|
||
height: 100vh;
|
||
max-width: none;
|
||
max-height: none;
|
||
background-color: #000;
|
||
border-radius: 0;
|
||
box-shadow: none;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
padding-top: env(safe-area-inset-top);
|
||
height: calc(100vh - env(safe-area-inset-top));
|
||
}
|
||
.video-player-wrapper {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100%;
|
||
margin: 0;
|
||
padding: 0;
|
||
overflow: hidden;
|
||
height: calc(100% - env(safe-area-inset-top));
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.floating-close-btn {
|
||
position: absolute;
|
||
top: env(safe-area-inset-top);
|
||
right: 0px;
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
border: none;
|
||
cursor: pointer;
|
||
color: #fff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.2s ease;
|
||
z-index: 2010;
|
||
backdrop-filter: blur(2px);
|
||
}
|
||
.floating-close-btn:hover {
|
||
background-color: rgba(255, 255, 255, 0.2);
|
||
transform: scale(1.1);
|
||
}
|
||
.video-modal-body {
|
||
padding: 0;
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #000;
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100%;
|
||
margin: 0;
|
||
overflow: hidden;
|
||
}
|
||
.video-player {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
margin: 0;
|
||
padding: 0;
|
||
border: none;
|
||
outline: none;
|
||
display: block;
|
||
}
|
||
.video-delete-btn {
|
||
position: fixed;
|
||
top: 50%;
|
||
right: 20px;
|
||
transform: translateY(-50%);
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 50%;
|
||
background-color: rgba(0, 0, 0, 0.3);
|
||
border: none;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #ffffff;
|
||
transition: all 0.2s ease;
|
||
z-index: 2010;
|
||
backdrop-filter: blur(2px);
|
||
}
|
||
.video-delete-btn:hover,
|
||
.video-delete-btn:active {
|
||
background-color: rgba(244, 67, 54, 0.5);
|
||
transform: translateY(-50%) scale(1.1);
|
||
}
|
||
.video-loading {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #fff;
|
||
padding: 40px 20px;
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
.loading-spinner {
|
||
width: 40px;
|
||
height: 40px;
|
||
border: 3px solid rgba(255, 255, 255, 0.2);
|
||
border-radius: 50%;
|
||
border-top-color: #fff;
|
||
animation: spin 1s ease-in-out infinite;
|
||
margin-bottom: 16px;
|
||
}
|
||
@keyframes spin {
|
||
to {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
.video-error {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #fff;
|
||
padding: 40px 20px;
|
||
text-align: center;
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
.video-error p {
|
||
margin: 16px 0 24px;
|
||
font-size: 14px;
|
||
}
|
||
.retry-btn {
|
||
padding: 8px 16px;
|
||
background-color: #2196f3;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
.retry-btn:hover {
|
||
background-color: #1976d2;
|
||
}
|
||
|
||
/* ========== 关注列表样式【彻底修复:无数据不居中,页面布局正常】 ========== */
|
||
.follow-tab {
|
||
padding: 0 5px;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
/* 关键:让tab容器占满父级高度 */
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100vh;
|
||
}
|
||
/* 搜索+操作栏 */
|
||
.follow-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin: 10px 0;
|
||
width: 100%;
|
||
z-index: 10;
|
||
position: relative;
|
||
/* 固定在顶部,不随滚动移动 */
|
||
flex-shrink: 0;
|
||
}
|
||
.search-wrapper {
|
||
flex: 1;
|
||
position: relative;
|
||
}
|
||
.follow-search-input {
|
||
height: 40px !important;
|
||
border-radius: 20px !important;
|
||
padding: 0 16px !important;
|
||
font-size: 14px !important;
|
||
width: 100% !important;
|
||
}
|
||
.follow-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
flex-shrink: 0;
|
||
}
|
||
.follow-btn {
|
||
width: 40px !important;
|
||
height: 40px !important;
|
||
border-radius: 50% !important;
|
||
padding: 0 !important;
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
justify-content: center !important;
|
||
}
|
||
/* Tab导航:横向滚动 */
|
||
.follow-tab-wrapper {
|
||
width: 100%;
|
||
overflow-x: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
margin-bottom: 10px;
|
||
z-index: 10;
|
||
position: relative;
|
||
/* 固定在搜索栏下方,不随滚动移动 */
|
||
flex-shrink: 0;
|
||
}
|
||
.follow-tab-scroll {
|
||
width: max-content;
|
||
padding-right: 10px;
|
||
}
|
||
.follow-custom-tabs {
|
||
--ant-tabs-nav-item-spacing: 16px !important;
|
||
}
|
||
:deep(.follow-custom-tabs .ant-tabs-tab) {
|
||
padding: 8px 12px !important;
|
||
font-size: 14px !important;
|
||
white-space: nowrap;
|
||
}
|
||
:deep(.follow-custom-tabs .ant-tabs-ink-bar) {
|
||
background-color: #4caf50 !important;
|
||
}
|
||
/* 列表容器:核心修复★★★ 固定高度+flex布局+滚动,无数据时不居中 */
|
||
.follow-list-container {
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px !important;
|
||
/* 适配底部导航,预留高度,固定计算方式 */
|
||
height: calc(100vh - 180px) !important;
|
||
max-height: calc(100vh - 180px) !important;
|
||
overflow-y: auto !important;
|
||
padding: 0 2px 20px 2px !important;
|
||
box-sizing: border-box;
|
||
-webkit-overflow-scrolling: touch;
|
||
/* 关键:让容器占满剩余高度,子元素按flex布局排列 */
|
||
flex: 1;
|
||
flex-grow: 1;
|
||
/* 关键:确保容器高度不超过视口高度 */
|
||
position: relative;
|
||
}
|
||
/* 博主卡片【核心修复:完整显示所有内容】 */
|
||
.follow-card {
|
||
border-radius: 12px !important;
|
||
border: 1px solid #f0f0f0 !important;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04) !important;
|
||
background: #fff !important;
|
||
overflow: visible !important;
|
||
margin: 0 !important;
|
||
width: 100% !important;
|
||
box-sizing: border-box !important;
|
||
position: relative !important;
|
||
z-index: 1 !important;
|
||
/* 卡片不拉伸,固定高度 */
|
||
flex-shrink: 0;
|
||
}
|
||
/* 非关注卡片样式 */
|
||
.follow-card.no-followed-card {
|
||
border-color: #fee2e2 !important;
|
||
background-color: #fef2f2 !important;
|
||
}
|
||
/* 卡片内层容器【强制显示所有子元素】 */
|
||
.follow-card-inner {
|
||
padding: 16px 14px !important;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px !important;
|
||
width: 100% !important;
|
||
box-sizing: border-box !important;
|
||
position: relative !important;
|
||
z-index: 2 !important;
|
||
height: auto !important;
|
||
min-height: 120px !important; /* 最小高度,防止内容过少塌陷 */
|
||
}
|
||
/* 卡片顶部:头像+名称+操作 */
|
||
.follow-card-top {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
flex-shrink: 0;
|
||
}
|
||
.avatar-wrapper {
|
||
width: 52px;
|
||
height: 52px;
|
||
flex-shrink: 0;
|
||
cursor: pointer;
|
||
border-radius: 50%;
|
||
overflow: hidden;
|
||
}
|
||
/* 头像样式覆盖 */
|
||
:deep(.follow-card .ant-avatar) {
|
||
width: 52px !important;
|
||
height: 52px !important;
|
||
font-size: 20px !important;
|
||
border-radius: 50% !important;
|
||
}
|
||
:deep(.follow-card .ant-avatar-lg) {
|
||
width: 52px !important;
|
||
height: 52px !important;
|
||
}
|
||
.avatar-placeholder {
|
||
background: linear-gradient(135deg, #4096ff, #69b1ff) !important;
|
||
color: #fff !important;
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
justify-content: center !important;
|
||
}
|
||
/* 名称+操作容器:自动占满剩余宽度 */
|
||
.name-actions {
|
||
flex: 1 !important;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
width: calc(100% - 62px) !important;
|
||
box-sizing: border-box;
|
||
}
|
||
.name-wrapper {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
flex-wrap: wrap;
|
||
width: 100%;
|
||
}
|
||
.uper-name {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #1d2129;
|
||
margin: 0;
|
||
line-height: 1.4;
|
||
flex: 1;
|
||
}
|
||
.no-followed-badge {
|
||
background-color: #fee2e2;
|
||
color: #dc2626;
|
||
font-size: 11px;
|
||
padding: 1px 6px;
|
||
border-radius: 4px;
|
||
flex-shrink: 0;
|
||
height: 20px;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
/* 顶部操作按钮:删除+开关 */
|
||
.top-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
justify-content: flex-end;
|
||
width: 100%;
|
||
}
|
||
.delete-btn {
|
||
color: #ef4444 !important;
|
||
font-size: 16px !important;
|
||
width: 32px !important;
|
||
height: 32px !important;
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
justify-content: center !important;
|
||
}
|
||
/* 签名样式:强制显示 */
|
||
.follow-card-desc {
|
||
font-size: 13px;
|
||
color: #86909c;
|
||
line-height: 1.5;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
padding: 0 2px;
|
||
flex-shrink: 0;
|
||
}
|
||
.signature-text {
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 1;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
width: 100%;
|
||
display: block !important;
|
||
}
|
||
/* 卡片底部:路径+全量同步【仅同步开启显示】 */
|
||
.follow-card-bottom {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px !important;
|
||
padding-top: 5px !important;
|
||
border-top: 1px solid #f5f5f5;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
flex-shrink: 0;
|
||
}
|
||
.path-area {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
}
|
||
.path-text {
|
||
font-size: 13px;
|
||
color: #333;
|
||
flex: 1;
|
||
line-height: 1.4;
|
||
}
|
||
.path-text.path-empty {
|
||
color: #999;
|
||
font-style: italic;
|
||
}
|
||
.edit-btn {
|
||
color: #4096ff !important;
|
||
font-size: 14px !important;
|
||
width: 32px !important;
|
||
height: 32px !important;
|
||
}
|
||
/* 编辑输入框 */
|
||
.edit-input-group {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
flex: 1;
|
||
width: 100%;
|
||
}
|
||
.path-input {
|
||
height: 32px !important;
|
||
font-size: 13px !important;
|
||
flex: 1 !important;
|
||
}
|
||
/* 全量同步 */
|
||
.full-sync-area {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
justify-content: space-between;
|
||
width: 100%;
|
||
}
|
||
.full-sync-label {
|
||
font-size: 13px;
|
||
color: #666;
|
||
}
|
||
|
||
/* 状态容器:核心修复★★★ 加载/无数据/无更多 统一样式,占满剩余高度且内容居中 */
|
||
.loading-container,
|
||
.empty-container,
|
||
.no-more-container {
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
/* 关键:占满列表容器剩余高度,内容垂直水平居中 */
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin: 0 !important;
|
||
padding: 20px 0 !important;
|
||
}
|
||
/* 加载状态 */
|
||
.loading-container {
|
||
gap: 12px;
|
||
color: #6b7280;
|
||
font-size: 14px;
|
||
}
|
||
/* 无更多状态(取消居中,靠上显示) */
|
||
.no-more-container {
|
||
flex: none !important; /* 不占满高度,仅自身高度 */
|
||
padding: 10px 0 !important;
|
||
color: #9ca3af;
|
||
font-size: 14px;
|
||
}
|
||
/* 无数据状态 */
|
||
.empty-container {
|
||
padding: 40px 0 !important;
|
||
}
|
||
/* 黑暗模式下无数据文字颜色 */
|
||
html.dark-mode .empty-container :deep(.ant-empty-description) {
|
||
color: #808099 !important;
|
||
}
|
||
|
||
/* ========== 小屏手机适配(375px以下,如SE/小屏安卓) ========== */
|
||
@media screen and (max-width: 375px) {
|
||
.follow-list-container {
|
||
/* 小屏减少预留高度,适配更友好 */
|
||
height: calc(100vh - 160px) !important;
|
||
max-height: calc(100vh - 160px) !important;
|
||
gap: 10px !important;
|
||
}
|
||
.follow-card-inner {
|
||
padding: 14px 12px !important;
|
||
gap: 10px !important;
|
||
min-height: 100px !important;
|
||
}
|
||
.avatar-wrapper {
|
||
width: 48px !important;
|
||
height: 48px !important;
|
||
}
|
||
:deep(.follow-card .ant-avatar) {
|
||
width: 48px !important;
|
||
height: 48px !important;
|
||
font-size: 18px !important;
|
||
}
|
||
.uper-name {
|
||
font-size: 15px !important;
|
||
}
|
||
.follow-card-desc {
|
||
font-size: 12px !important;
|
||
}
|
||
.full-sync-label {
|
||
font-size: 12px !important;
|
||
}
|
||
}
|
||
/* 黑暗模式下无数据文字颜色 */
|
||
html.dark-mode .empty-container :deep(.ant-empty-description) {
|
||
color: #808099 !important;
|
||
}
|
||
|
||
/* ========== 黑暗模式适配【完整适配follow-card】 ========== */
|
||
html.dark-mode .stats-dashboard-mobile {
|
||
background-color: #1a1a2e;
|
||
color: #eaeaea;
|
||
padding-bottom: 80px;
|
||
}
|
||
html.dark-mode .main-card {
|
||
border-color: rgba(255, 255, 255, 0.1);
|
||
background-color: rgba(30, 30, 50, 0.9);
|
||
}
|
||
html.dark-mode .stat-subitems {
|
||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||
}
|
||
html.dark-mode .subitem {
|
||
background: rgba(40, 40, 65, 0.7);
|
||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||
}
|
||
html.dark-mode .subitem:hover {
|
||
background: rgba(40, 40, 65, 0.9);
|
||
border-color: rgba(255, 255, 255, 0.1);
|
||
}
|
||
html.dark-mode .stat-meta {
|
||
color: #b0b0c3;
|
||
}
|
||
html.dark-mode .stat-value {
|
||
color: #ffffff;
|
||
}
|
||
html.dark-mode .value-label {
|
||
color: #d0d0d0;
|
||
}
|
||
html.dark-mode .size-count {
|
||
color: #d0d0d0;
|
||
}
|
||
html.dark-mode .unit {
|
||
color: #d0d0d0;
|
||
}
|
||
html.dark-mode .stat-icon {
|
||
background-color: rgba(76, 175, 80, 0.25);
|
||
}
|
||
html.dark-mode .secondary-card .stat-icon {
|
||
background-color: rgba(33, 150, 243, 0.25);
|
||
}
|
||
html.dark-mode .subitem-meta {
|
||
color: #c0c0d3;
|
||
}
|
||
html.dark-mode .subitem-count {
|
||
color: #ffffff;
|
||
}
|
||
html.dark-mode .subitem-size {
|
||
color: #a0a0b3;
|
||
}
|
||
html.dark-mode .subitem-value {
|
||
color: #ffffff;
|
||
}
|
||
html.dark-mode .subitem-value .unit {
|
||
color: #b0b0c3;
|
||
}
|
||
html.dark-mode .subitem-icon {
|
||
background-color: rgba(233, 30, 99, 0.2);
|
||
}
|
||
html.dark-mode .collect-icon {
|
||
background-color: rgba(255, 152, 0, 0.2);
|
||
}
|
||
html.dark-mode .follow-icon {
|
||
background-color: rgba(156, 39, 176, 0.2);
|
||
}
|
||
html.dark-mode .graphic-icon {
|
||
background-color: rgba(255, 159, 64, 0.2);
|
||
}
|
||
html.dark-mode .video-list-icon {
|
||
background-color: rgba(33, 150, 243, 0.25);
|
||
}
|
||
html.dark-mode .video-time {
|
||
color: #a0a0b3;
|
||
}
|
||
html.dark-mode .empty-video-list {
|
||
color: #808099;
|
||
}
|
||
/* 黑暗模式 - 底部导航 */
|
||
html.dark-mode .bottom-nav {
|
||
background-color: #16213e;
|
||
border-top-color: rgba(255, 255, 255, 0.1);
|
||
}
|
||
html.dark-mode .nav-item {
|
||
color: #b0b0c3;
|
||
}
|
||
html.dark-mode .nav-item.active {
|
||
color: #4caf50;
|
||
}
|
||
/* 黑暗模式 - 日志样式 */
|
||
html.dark-mode .log-title {
|
||
color: #ffffff;
|
||
}
|
||
html.dark-mode .filter-btn {
|
||
background-color: #1f4068;
|
||
border-color: rgba(255, 255, 255, 0.1);
|
||
color: #e0e0e0;
|
||
}
|
||
html.dark-mode .filter-btn.active {
|
||
background-color: #4caf50;
|
||
color: #ffffff;
|
||
border-color: #4caf50;
|
||
}
|
||
html.dark-mode .log-item {
|
||
background-color: rgba(30, 30, 50, 0.9);
|
||
border-color: rgba(255, 255, 255, 0.05);
|
||
}
|
||
html.dark-mode .log-file {
|
||
color: #c0c0d3;
|
||
}
|
||
html.dark-mode .empty-log {
|
||
color: #808099;
|
||
}
|
||
/* 黑暗模式 - 弹窗样式 */
|
||
html.dark-mode .log-modal-content {
|
||
background-color: #1e1e3f;
|
||
border-color: rgba(255, 255, 255, 0.1);
|
||
}
|
||
html.dark-mode .log-modal-header,
|
||
html.dark-mode .log-modal-footer {
|
||
border-color: rgba(255, 255, 255, 0.1);
|
||
}
|
||
html.dark-mode .modal-date {
|
||
color: #c0c0d3;
|
||
}
|
||
html.dark-mode .file-name {
|
||
color: #ffffff;
|
||
border-color: rgba(255, 255, 255, 0.1);
|
||
}
|
||
html.dark-mode .log-content {
|
||
background-color: #2a2a4a;
|
||
color: #eaeaea;
|
||
}
|
||
html.dark-mode .close-btn {
|
||
color: #c0c0d3;
|
||
}
|
||
html.dark-mode .close-btn:hover {
|
||
background-color: #2a2a4a;
|
||
color: #ffffff;
|
||
}
|
||
/* 黑暗模式 - 视频弹窗 */
|
||
html.dark-mode .video-delete-btn {
|
||
background-color: rgba(0, 0, 0, 0.4);
|
||
}
|
||
html.dark-mode .video-delete-btn:hover,
|
||
html.dark-mode .video-delete-btn:active {
|
||
background-color: rgba(244, 67, 54, 0.6);
|
||
}
|
||
/* 黑暗模式 - 关注列表【完整适配】 */
|
||
html.dark-mode .follow-card {
|
||
border-color: rgba(255, 255, 255, 0.1) !important;
|
||
background-color: rgba(30, 30, 50, 0.9) !important;
|
||
}
|
||
html.dark-mode .follow-card.no-followed-card {
|
||
border-color: #7f1d1d !important;
|
||
background-color: rgba(127, 29, 29, 0.2) !important;
|
||
}
|
||
html.dark-mode .uper-name {
|
||
color: #f3f4f6 !important;
|
||
}
|
||
html.dark-mode .no-followed-badge {
|
||
background-color: #7f1d1d !important;
|
||
color: #fecaca !important;
|
||
}
|
||
html.dark-mode .follow-card-desc {
|
||
color: #9ca3af !important;
|
||
}
|
||
html.dark-mode .follow-card-bottom {
|
||
border-top-color: rgba(255, 255, 255, 0.05) !important;
|
||
}
|
||
html.dark-mode .path-text {
|
||
color: #d1d5db !important;
|
||
}
|
||
html.dark-mode .path-text.path-empty {
|
||
color: #6b7280 !important;
|
||
}
|
||
html.dark-mode .full-sync-label {
|
||
color: #9ca3af !important;
|
||
}
|
||
html.dark-mode .delete-btn {
|
||
color: #fecaca !important;
|
||
}
|
||
html.dark-mode .edit-btn {
|
||
color: #60a5fa !important;
|
||
}
|
||
html.dark-mode .avatar-placeholder {
|
||
background: linear-gradient(135deg, #3b82f6, #60a5fa) !important;
|
||
}
|
||
|
||
/* ========== 小屏手机适配(375px以下,如SE/小屏安卓) ========== */
|
||
@media screen and (max-width: 375px) {
|
||
.follow-list-container {
|
||
max-height: calc(100vh - 160px) !important;
|
||
gap: 10px !important;
|
||
}
|
||
.follow-card-inner {
|
||
padding: 14px 12px !important;
|
||
gap: 10px !important;
|
||
min-height: 100px !important;
|
||
}
|
||
.avatar-wrapper {
|
||
width: 48px !important;
|
||
height: 48px !important;
|
||
}
|
||
:deep(.follow-card .ant-avatar) {
|
||
width: 48px !important;
|
||
height: 48px !important;
|
||
font-size: 18px !important;
|
||
}
|
||
.uper-name {
|
||
font-size: 15px !important;
|
||
}
|
||
.follow-card-desc {
|
||
font-size: 12px !important;
|
||
}
|
||
.full-sync-label {
|
||
font-size: 12px !important;
|
||
}
|
||
}
|
||
|
||
/* 筛选下拉选择器:和搜索框、同步按钮统一风格,移动端适配 */
|
||
.follow-filter-select {
|
||
width: 120px !important;
|
||
height: 40px !important;
|
||
flex-shrink: 0;
|
||
}
|
||
/* 覆盖antd select默认样式,和布局匹配 */
|
||
:deep(.follow-filter-select .ant-select-selector) {
|
||
height: 100% !important;
|
||
border-radius: 20px !important; /* 和搜索框同圆角,视觉统一 */
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
padding: 0 12px !important;
|
||
font-size: 13px !important;
|
||
}
|
||
/* 隐藏select默认边框高亮(可选,更简洁) */
|
||
:deep(.follow-filter-select .ant-select-focused .ant-select-selector) {
|
||
box-shadow: none !important;
|
||
border-color: #d9d9d9 !important;
|
||
}
|
||
/* 下拉选项弹窗:适配移动端,调大宽度 */
|
||
:deep(.follow-filter-select .ant-select-dropdown) {
|
||
min-width: 120px !important;
|
||
font-size: 13px !important;
|
||
}
|
||
|
||
/* 黑暗模式适配:筛选选择器样式统一 */
|
||
html.dark-mode :deep(.follow-filter-select .ant-select-selector) {
|
||
background-color: #1f4068 !important;
|
||
border-color: rgba(255, 255, 255, 0.1) !important;
|
||
color: #e0e0e0 !important;
|
||
}
|
||
html.dark-mode :deep(.follow-filter-select .ant-select-dropdown) {
|
||
background-color: #1e1e3f !important;
|
||
border-color: rgba(255, 255, 255, 0.1) !important;
|
||
}
|
||
html.dark-mode :deep(.follow-filter-select .ant-select-item) {
|
||
color: #e0e0e0 !important;
|
||
}
|
||
html.dark-mode :deep(.follow-filter-select .ant-select-item-selected) {
|
||
background-color: #4caf50 !important;
|
||
color: #fff !important;
|
||
}
|
||
</style> |