完成前端改造
1、下一步实现各个分类下载视频中重复视频去除-可配置是否去重
This commit is contained in:
+9
-11
@@ -17,7 +17,7 @@ namespace dy.net
|
||||
public class Program
|
||||
{
|
||||
// 常量定义
|
||||
private static string DefaultListenUrl = "http://*:10102";
|
||||
private static string DefaultListenUrl = "http://*:10101";
|
||||
private const string SpaRootPath = "app/dist";
|
||||
private const string SpaSourcePath = "app/";
|
||||
private const string SwaggerDocTitle = "dy.net WebApi Docs";
|
||||
@@ -33,14 +33,13 @@ namespace dy.net
|
||||
// 构建Web应用
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
//from docker yaml file 环境变量 或者 dockerfile 或appsettings.json
|
||||
DefaultListenUrl = builder.Configuration.GetValue<string>("ASPNETCORE_URLS") ?? DefaultListenUrl;
|
||||
var downImgConfig = builder.Configuration.GetValue<string>("DOWN_IMGVIDEO");
|
||||
DefaultListenUrl = builder.Configuration.GetValue<string>(SystemStaticUtil.ASPNETCORE_URLS) ?? DefaultListenUrl;
|
||||
var downImgConfig = builder.Configuration.GetValue<string>(SystemStaticUtil.DOWN_IMAGE_VIDEO_ENABLE);
|
||||
|
||||
Console.WriteLine("DOWN_IMGVIDEO=" + downImgConfig);
|
||||
//Console.WriteLine("DOWN_IMGVIDEO=" + downImgConfig);
|
||||
if (!string.IsNullOrEmpty(downImgConfig))
|
||||
{
|
||||
downImgConfig = downImgConfig.ToLower();
|
||||
downImageVideo = downImgConfig == "1" || downImgConfig == "y"||downImgConfig=="t"||downImgConfig=="true";
|
||||
downImageVideo = downImgConfig.ToLower() == "1" ;
|
||||
}
|
||||
var isDevelopment = builder.Environment.IsDevelopment();
|
||||
|
||||
@@ -196,7 +195,7 @@ namespace dy.net
|
||||
var commonService = services.GetRequiredService<DouyinCommonService>();
|
||||
var config = commonService.InitConfig(downImageVideo);
|
||||
|
||||
// 更新收藏视频类型--兼容老版本-原来的旧数据没有这个类型字段
|
||||
// 更新视频类型--兼容老版本
|
||||
commonService.UpdateCollectViedoType();
|
||||
// 重置博主作品同步状态为未同步
|
||||
commonService.UpdateAllCookieSyncedToZero();
|
||||
@@ -204,13 +203,12 @@ namespace dy.net
|
||||
var quartzJobService = services.GetRequiredService<DouyinQuartzJobService>();
|
||||
quartzJobService.StartJob(config?.Cron ?? "30");
|
||||
|
||||
Serilog.Log.Debug("系统初始化完成,会默认将-博主作品同步功能-同步全部作品重置为关闭(若要开启,可以到抖音授权页面中修改)");
|
||||
Serilog.Log.Debug($"默认设置的每次读取行数为:{config.BatchCount},可前往系统配置页修改");
|
||||
Serilog.Log.Debug("系统初始化,默认将-博主作品-同步全部-配置为关闭,可到授权页面中调整(不建议开启全量同步)");
|
||||
Serilog.Log.Debug($"默认每次读取行数为:{config.BatchCount},可前往系统配置修改");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var logger = services.GetRequiredService<ILogger<Program>>();
|
||||
logger.LogError(ex, "Failed to initialize services on startup");
|
||||
Serilog.Log.Error(ex, "Failed to initialize services on startup");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "http://localhost:5193"
|
||||
"applicationUrl": "http://localhost:10101"
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
|
||||
Binary file not shown.
+1
-1
@@ -5,7 +5,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/logo.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>抖音同步小帮手</title>
|
||||
<title>抖小云</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
+5
-4
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<ThemeProvider is-root v-bind="themeConfig" :apply-style="false">
|
||||
<stepin-view system-name="抖音小帮手" :class="`${contentClass}`" :user="user" :navMode="navigation" :useTabs="useTabs" :themeList="themeList" v-model:show-setting="showSetting" v-model:theme="theme" @themeSelect="configTheme" logo-src="@/assets/logo.png">
|
||||
<stepin-view system-name="抖小云" :class="`${contentClass}`" :user="user" :navMode="navigation" :useTabs="useTabs" :themeList="themeList" v-model:show-setting="showSetting" v-model:theme="theme" @themeSelect="configTheme" logo-src="@/assets/logo.png">
|
||||
<template #headerActions>
|
||||
<HeaderActions @showSetting="showSetting = true" />
|
||||
</template>
|
||||
@@ -16,7 +16,7 @@
|
||||
</stepin-view>
|
||||
</ThemeProvider>
|
||||
<my-personal ref="personalRef" />
|
||||
<email-set ref="emailRef" />
|
||||
<!-- <email-set ref="emailRef" /> -->
|
||||
<!-- <login-modal :unless="['/login']" /> -->
|
||||
</template>
|
||||
|
||||
@@ -28,7 +28,7 @@ import avatar from '@/assets/avatar.png';
|
||||
import { PageFooter, HeaderActions } from '@/components/layout';
|
||||
import Setting from './components/setting';
|
||||
import { LoginModal } from '@/pages/login';
|
||||
import { MyPersonal, EmailSet } from '@/pages/personal';
|
||||
import { MyPersonal } from '@/pages/personal';
|
||||
import { configTheme, themeList } from '@/theme';
|
||||
import { ThemeProvider } from 'stepin';
|
||||
// logout,profile
|
||||
@@ -124,7 +124,8 @@ onMounted(() => {
|
||||
background: theme('backgroundColor.layout');
|
||||
}
|
||||
.system-name {
|
||||
// color: #16213e !important;
|
||||
color: #722ed1 !important;
|
||||
margin-left: 0em !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<svg width="90px" height="90px" viewBox="0 0 90 90" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 61.2 (89653) - https://sketch.com -->
|
||||
<title>logo-black</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="LOGO" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Artboard" transform="translate(-532.000000, -80.000000)">
|
||||
<g id="logo-black" transform="translate(532.000000, 80.000000)">
|
||||
<g id="Group">
|
||||
<circle id="Combined-Shape" fill="#C71D23" cx="44.8544363" cy="44.8544363" r="44.8544363"></circle>
|
||||
<path d="M67.558546,39.8714292 L42.0857966,39.8714292 C40.8627004,39.8720094 39.8710953,40.8633548 39.8701949,42.0864508 L39.8687448,47.623783 C39.867826,48.8471055 40.8592652,49.8390642 42.0825879,49.8393845 C42.0827874,49.8393846 42.0829869,49.8393846 42.0831864,49.8387862 L57.5909484,49.838657 C58.8142711,49.8386283 59.8059783,50.830319 59.8059885,52.0536417 C59.8059885,52.0536479 59.8059885,52.053654 59.8059701,52.0536602 L59.8059701,52.6073539 L59.8059701,52.6073539 L59.8059701,53.161115 C59.8059701,56.8310831 56.8308731,59.80618 53.160905,59.80618 L32.1165505,59.80618 C30.8934034,59.806119 29.9018373,58.8145802 29.9017425,57.5914331 L29.9011625,36.5491188 C29.9008781,32.8791508 32.8758931,29.9039718 36.5458611,29.9038706 C36.5459222,29.9038706 36.5459833,29.9038706 36.5460443,29.9040538 L67.5523638,29.9040538 C68.77515,29.9026795 69.7666266,28.9118177 69.7687593,27.6890325 L69.7721938,22.1516997 C69.774326,20.928378 68.7832423,19.9360642 67.5599198,19.9353054 C67.5594619,19.9353051 67.5590039,19.935305 67.558546,19.9366784 L36.5479677,19.9366784 C27.3730474,19.9366784 19.935305,27.3744208 19.935305,36.549341 L19.935305,67.558546 C19.935305,68.7818687 20.927004,69.7735676 22.1503267,69.7735676 L54.8224984,69.7735676 C63.079746,69.7735676 69.7735676,63.079746 69.7735676,54.8224984 L69.7735676,42.0864509 C69.7735676,40.8631282 68.7818687,39.8714292 67.558546,39.8714292 Z" id="G" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -49,13 +49,13 @@ const navList = [
|
||||
];
|
||||
</script>
|
||||
<template>
|
||||
<ThemeProvider :color="{ middle: { 'bg-base': '#1896ff' }, primary: { DEFAULT: '#1896ff' } }" :autoAdapt="true">
|
||||
<ThemeProvider :color="{ middle: { 'bg-base': '#fff' }, primary: { DEFAULT: '#fff' } }" :autoAdapt="true">
|
||||
<div class="front-view flex flex-col">
|
||||
<div class="text-text flex-1">
|
||||
<div class="front-header flex items-baseline py-md px-xl">
|
||||
<div class="text-xxl text-text hover:text-text">
|
||||
<!-- <img src="@/assets/png.svg" /> -->
|
||||
<!-- 抖音同步小帮手 -->
|
||||
<!-- 抖小云 -->
|
||||
</div>
|
||||
<!-- <div style="width: calc(100% - 430px)" class="front-navigation mx-xl flex overflow-hidden items-center text-lg overflow-ellipsis whitespace-nowrap">
|
||||
<div :class="`front-nav-item flex items-center cursor-pointer mx-base ${nav.children ? 'with-list' : ''}`" v-for="nav in navList">
|
||||
|
||||
@@ -30,7 +30,7 @@ onMounted(() => {
|
||||
<div class="front-view flex flex-col" style="background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)">
|
||||
<!-- <div class="text-xxl text-text hover:text-text" style="margin-left:20px;color:#722ed1;"> -->
|
||||
<!-- <img src="/logo.png" /> -->
|
||||
<!-- 抖音同步小帮手 -->
|
||||
<!-- 抖小云 -->
|
||||
<!-- </div> -->
|
||||
<div class="front-content ">
|
||||
<router-view />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from 'vue';
|
||||
import { StepinHeaderAction } from 'stepin';
|
||||
import Notice from '@/components/notice/Notice.vue';
|
||||
// import Notice from '@/components/notice/Notice.vue';
|
||||
import DayNightSwitch from '@/components/switch/DayNightSwitch.vue';
|
||||
import { BellOutlined } from '@ant-design/icons-vue';
|
||||
import Fullscreen from '../fullscreen/Fullscreen.vue';
|
||||
@@ -47,20 +47,15 @@ const noticeList = reactive([
|
||||
|
||||
<!-- </StepinHeaderAction> -->
|
||||
<!-- <StepinHeaderAction>
|
||||
<a class="action-item" href="http://github.com/stepui/stepin-template" target="_blank">
|
||||
<a class="action-item" href="https://github.com/jianzhichu/dysync.net" target="_blank">
|
||||
<GithubOutlined />
|
||||
</a>
|
||||
</StepinHeaderAction>
|
||||
<StepinHeaderAction>
|
||||
<a class="action-item" href="http://gitee.com/stepui/stepin-template" target="_blank">
|
||||
<a class="action-item" href="https://gitee.com/deathvicky/dysync.net" target="_blank">
|
||||
<img class="gitee-logo" src="@/assets/gitee.svg" />
|
||||
</a>
|
||||
</StepinHeaderAction> -->
|
||||
<!-- <StepinHeaderAction>
|
||||
<div class="action-item setting" @click="$emit('showSetting')">
|
||||
<SettingOutlined />
|
||||
</div>
|
||||
</StepinHeaderAction> -->
|
||||
|
||||
<!-- <a-popover placement="bottomRight">
|
||||
<StepinHeaderAction>
|
||||
@@ -72,8 +67,14 @@ const noticeList = reactive([
|
||||
<Notice :data-source="noticeList" />
|
||||
</template>
|
||||
</a-popover> -->
|
||||
<!-- <StepinHeaderAction>
|
||||
<StepinHeaderAction>
|
||||
<Fullscreen class="-mx-xs -my-sm h-[56px] px-xs py-sm flex items-center" target=".stepin-layout" />
|
||||
</StepinHeaderAction>
|
||||
<!-- <StepinHeaderAction>
|
||||
@click="$emit('showSetting')"
|
||||
<div class="action-item setting">
|
||||
<SettingOutlined />
|
||||
</div>
|
||||
</StepinHeaderAction> -->
|
||||
</template>
|
||||
<style scoped lang="less">
|
||||
|
||||
@@ -1,41 +1,108 @@
|
||||
<script lang="ts" setup></script>
|
||||
<script lang="ts" setup>
|
||||
import { GithubOutlined, CopyrightOutlined } from '@ant-design/icons-vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
// 响应式变量存储年份文本
|
||||
const yearText = ref('');
|
||||
|
||||
onMounted(() => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const startYear = 2025;
|
||||
|
||||
if (currentYear <= startYear) {
|
||||
yearText.value = startYear.toString();
|
||||
} else {
|
||||
yearText.value = `${startYear}-${currentYear}`;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-footer">
|
||||
<div class="links">
|
||||
<!-- <a class="link" href="https://github.com/stepui/stepin-template" target="_blank"> Stepin 首页 </a> -->
|
||||
<!-- <a class="link" href="https://github.com/stepui/stepin-template" target="_blank">
|
||||
<GithubOutlined />
|
||||
</a> -->
|
||||
<!-- <a class="link" href="https://www.antdv.com/docs/vue/introduce-cn/" target="_blank"> Ant Design </a> -->
|
||||
<footer class="page-footer">
|
||||
<div class="footer-content">
|
||||
<!-- 版权信息容器 -->
|
||||
<div class="copyright">
|
||||
<CopyrightOutlined class="icon-copyright" />
|
||||
<span>{{ yearText }} 抖小云 dysync.net. </span>
|
||||
|
||||
<!-- 将社交链接移动到这里 -->
|
||||
<div class="social-links">
|
||||
<a href="https://github.com/jianzhichu/dysync.net" target="_blank" rel="noopener noreferrer" aria-label="访问我们的 GitHub 仓库" class="social-link">
|
||||
<GithubOutlined />
|
||||
</a>
|
||||
<a href="https://gitee.com/deathvicky/dysync.net" target="_blank" rel="noopener noreferrer" aria-label="访问我们的 Gitee 仓库" class="social-link">
|
||||
<img class="gitee-icon" src="@/assets/gitee.svg" alt="Gitee" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="copyright">
|
||||
Copyright
|
||||
<CopyrightOutlined class="icon-copyright" />
|
||||
2023 dy.net
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.page-footer {
|
||||
text-align: center;
|
||||
@apply text-gray-400;
|
||||
// 使用 :root 变量,便于主题切换
|
||||
--footer-text-color: var(--text-color-secondary, #999);
|
||||
--footer-hover-color: var(--primary-color, #1890ff);
|
||||
|
||||
.links {
|
||||
width: 100%;
|
||||
color: var(--footer-text-color);
|
||||
padding: 24px 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
|
||||
.footer-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// 版权信息现在是一个父 flex 容器
|
||||
.copyright {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
// 增大 gap 以分隔文字和社交链接组
|
||||
gap: 10px;
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.link {
|
||||
@apply hover:text-gray-400 pl-4 pr-4;
|
||||
// 社交链接组自身也是一个 flex 容器
|
||||
.social-links {
|
||||
display: flex;
|
||||
// 为两个图标之间设置间距
|
||||
gap: 16px;
|
||||
// 移除了原来的 margin-bottom
|
||||
|
||||
.social-link {
|
||||
color: inherit;
|
||||
transition: color 0.3s ease, transform 0.2s ease;
|
||||
display: inline-flex;
|
||||
opacity: 0.8;
|
||||
align-items: center;
|
||||
// 移除了图标和文字的 gap,因为现在没有文字了
|
||||
|
||||
&:hover {
|
||||
color: var(--footer-hover-color);
|
||||
opacity: 1;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
svg,
|
||||
img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.copyright {
|
||||
margin-top: 8px;
|
||||
|
||||
.icon-copyright {
|
||||
margin: 0;
|
||||
}
|
||||
.gitee-icon {
|
||||
width: 14px !important;
|
||||
height: 14px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -9,7 +9,6 @@ export type Type = 'day' | 'night';
|
||||
|
||||
const props = defineProps({
|
||||
value: { type: String as PropType<Type> },
|
||||
// nightColor: { type: String, default: '#1D1D1D' },
|
||||
nightColor: { type: String, default: '#1a1a2e' },
|
||||
});
|
||||
|
||||
@@ -17,37 +16,34 @@ const emit = defineEmits<{
|
||||
(e: 'update:value', value: Type): void;
|
||||
}>();
|
||||
|
||||
const STORAGE_KEY = 'theme-mode';
|
||||
|
||||
const getInitialValue = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const cachedValue = localStorage.getItem(STORAGE_KEY);
|
||||
if (cachedValue === 'day' || cachedValue === 'night') {
|
||||
return cachedValue;
|
||||
}
|
||||
}
|
||||
return props.value || 'day';
|
||||
};
|
||||
|
||||
const { value: _value } = useModelValue(
|
||||
() => props.value,
|
||||
(val) => emit('update:value', val),
|
||||
'day'
|
||||
getInitialValue()
|
||||
);
|
||||
|
||||
const switcher: { [key in Type]: Type } = {
|
||||
day: 'night',
|
||||
night: 'day',
|
||||
};
|
||||
|
||||
const { theme } = storeToRefs(useThemeStore());
|
||||
let cachedMiddleColors = {};
|
||||
|
||||
// 监听主题色变换,更新缓存
|
||||
let cachedMiddleColors = cloneDeep(theme.value.color.middle);
|
||||
watch(
|
||||
theme,
|
||||
(val) => {
|
||||
if (val.color.middle['bg-base'] !== props.nightColor) {
|
||||
cachedMiddleColors = cloneDeep(val.color.middle);
|
||||
_value.value = 'day';
|
||||
} else {
|
||||
_value.value = 'night';
|
||||
}
|
||||
console.log('theme', _value.value);
|
||||
updateHtmlClass(_value.value);
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
// 在你的主题store中添加切换逻辑
|
||||
const updateHtmlClass = (mode) => {
|
||||
// --- 修正点 1: 函数定义移到 watch 之前 ---
|
||||
const updateHtmlClass = (mode: Type) => {
|
||||
const html = document.documentElement;
|
||||
if (mode === 'night') {
|
||||
html.classList.add('dark-mode');
|
||||
@@ -55,14 +51,46 @@ const updateHtmlClass = (mode) => {
|
||||
html.classList.remove('dark-mode');
|
||||
}
|
||||
};
|
||||
// 主题颜色配置
|
||||
|
||||
// 监听 theme bg-base 变化,用于同步外部修改
|
||||
watch(
|
||||
() => theme.value?.color?.middle?.['bg-base'],
|
||||
(newBgColor) => {
|
||||
if (newBgColor) {
|
||||
if (newBgColor === props.nightColor) {
|
||||
_value.value = 'night';
|
||||
} else {
|
||||
_value.value = 'day';
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// --- 修正点 2: watch 现在可以安全地调用 updateHtmlClass ---
|
||||
watch(
|
||||
_value,
|
||||
(newMode) => {
|
||||
console.log('mode switched to:', newMode);
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(STORAGE_KEY, newMode);
|
||||
}
|
||||
updateHtmlClass(newMode);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// --- 修正点 3: 惰性缓存白天模式颜色 ---
|
||||
const colorCfg = computed(() => {
|
||||
if (_value.value === 'day') {
|
||||
if (Object.keys(cachedMiddleColors).length === 0) {
|
||||
cachedMiddleColors = cloneDeep(theme.value?.color?.middle || {});
|
||||
}
|
||||
return { middle: cachedMiddleColors };
|
||||
}
|
||||
return { middle: { 'bg-base': props.nightColor } };
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ThemeProvider is-root :color="colorCfg">
|
||||
<div @click="() => (_value = switcher[_value])" class="bg-fill-2 day-night-switch hover:border-border relative border-border-2 text-lg rounded-full border border-solid flex items-center">
|
||||
@@ -72,6 +100,7 @@ const colorCfg = computed(() => {
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.day-night-switch {
|
||||
.spot {
|
||||
@@ -91,4 +120,4 @@ const colorCfg = computed(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -11,17 +11,18 @@ import type { UnwrapRef } from 'vue';
|
||||
import { StarOutlined, StarFilled, StarTwoTone } from '@ant-design/icons-vue';
|
||||
const columns = [
|
||||
{
|
||||
title: 'Cookie名',
|
||||
title: 'Cookie名称',
|
||||
dataIndex: 'userName',
|
||||
width: 100,
|
||||
width: 180,
|
||||
},
|
||||
{ title: '状态', dataIndex: 'status', width: 80 },
|
||||
{ title: '收藏路径', dataIndex: 'savePath', width: 100 },
|
||||
{ title: '喜欢路径', dataIndex: 'favSavePath', width: 100 },
|
||||
{ title: 'Up主路径', dataIndex: 'upSavePath', width: 100 },
|
||||
// { title: 'Cookie', dataIndex: 'cookies' },
|
||||
{ title: 'Up主secUserId', dataIndex: 'upSecUserIds' },
|
||||
{ title: '用户secUserId', dataIndex: 'secUserId' },
|
||||
{ title: '状态', dataIndex: 'status', width: 180 },
|
||||
{ title: '收藏路径', dataIndex: 'savePath' },
|
||||
{ title: '喜欢路径', dataIndex: 'favSavePath' },
|
||||
{ title: '博主路径', dataIndex: 'upSavePath' },
|
||||
{ title: '图片视频', dataIndex: 'imgSavePath' },
|
||||
{ title: 'Cookie', dataIndex: 'cookies' },
|
||||
// { title: '博主信息', dataIndex: 'upSecUserIds' },
|
||||
// { title: '自己', dataIndex: 'secUserId' },
|
||||
{ title: '操作', dataIndex: 'edit', width: 200 },
|
||||
// { title: 'id', dataIndex: 'id', width: 200, hiden: false },
|
||||
];
|
||||
@@ -45,6 +46,7 @@ type DataItem = {
|
||||
upSecUserIdsJson?: UpSecUserIdItem[];
|
||||
upSecUserIds?: string;
|
||||
upSavePath?: string;
|
||||
imgSavePath?: string;
|
||||
};
|
||||
|
||||
// const dataSource = reactive<DataItem[]>([
|
||||
@@ -103,20 +105,21 @@ function addNew() {
|
||||
|
||||
const showModal = ref(false);
|
||||
|
||||
const newAuthor = (author?: DataItem) => {
|
||||
if (!author) {
|
||||
author = { _isNew: true };
|
||||
const newCookie = (cookie?: DataItem) => {
|
||||
if (!cookie) {
|
||||
cookie = { _isNew: true };
|
||||
}
|
||||
author.userName = undefined;
|
||||
author.cookies = undefined;
|
||||
author.savePath = undefined;
|
||||
author.favSavePath = undefined;
|
||||
author.secUserId = undefined;
|
||||
author.status = 0;
|
||||
author.id = '0';
|
||||
author.upSecUserIdsJson = undefined;
|
||||
author.upSavePath = undefined;
|
||||
return author;
|
||||
cookie.userName = undefined;
|
||||
cookie.cookies = undefined;
|
||||
cookie.savePath = undefined;
|
||||
cookie.favSavePath = undefined;
|
||||
cookie.secUserId = undefined;
|
||||
cookie.status = 0;
|
||||
cookie.id = '0';
|
||||
cookie.upSecUserIdsJson = undefined;
|
||||
cookie.upSavePath = undefined;
|
||||
cookie.imgSavePath = undefined;
|
||||
return cookie;
|
||||
};
|
||||
|
||||
const copyObject = (target: any, source?: any) => {
|
||||
@@ -126,10 +129,10 @@ const copyObject = (target: any, source?: any) => {
|
||||
Object.keys(target).forEach((key) => (target[key] = source[key]));
|
||||
};
|
||||
|
||||
const form = reactive<DataItem>(newAuthor());
|
||||
const form = reactive<DataItem>(newCookie());
|
||||
|
||||
function reset() {
|
||||
return newAuthor(form);
|
||||
return newCookie(form);
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
@@ -218,8 +221,8 @@ function edit(record: DataItem) {
|
||||
type Status = 0 | 1;
|
||||
|
||||
const StatusDict = {
|
||||
0: '关闭',
|
||||
1: '开启',
|
||||
0: '同步已关闭',
|
||||
1: '同步已开启',
|
||||
};
|
||||
|
||||
const dataSource = ref(datas);
|
||||
@@ -246,9 +249,9 @@ const removeRow = (index) => {
|
||||
const rowCount = 4;
|
||||
</script>
|
||||
<template>
|
||||
<a-modal :title="form._isNew ? '新增Cookie' : '编辑Cookie'" v-model:visible="showModal" @ok="submit" @cancel="cancel" width="1000px">
|
||||
<a-modal :title="form._isNew ? '新增' : '编辑'" v-model:visible="showModal" @ok="submit" @cancel="cancel" width="1000px">
|
||||
<a-form ref="formModel" :model="form" :labelCol="{ span: 3 }" :wrapperCol="{ span: 20 }">
|
||||
<a-form-item label="Cookie别名" required name="userName">
|
||||
<a-form-item label="Cookie名称" required name="userName">
|
||||
<a-input v-model:value="form.userName" />
|
||||
</a-form-item>
|
||||
<a-form-item label="id" required name="id" v-show="false">
|
||||
@@ -258,10 +261,10 @@ const rowCount = 4;
|
||||
<a-input v-model:value="form.savePath" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="Cookie" name="cookies">
|
||||
<a-form-item label="Cookie值" name="cookies">
|
||||
<a-textarea v-model:value="form.cookies" :rows='rowCount' />
|
||||
</a-form-item>
|
||||
<a-form-item label="喜欢的存储路径" name="favSavePath">
|
||||
<a-form-item label="喜欢视频存储路径" name="favSavePath">
|
||||
<div style="display: flex; align-items: center; gap: 6px;">
|
||||
<a-input v-model:value="form.favSavePath" style="flex: 1;" />
|
||||
<a-tooltip title="同步“我喜欢的”视频时,必填!!!">
|
||||
@@ -269,7 +272,7 @@ const rowCount = 4;
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="SecUserId" name="secUserId">
|
||||
<a-form-item label="我的secUserId" name="secUserId">
|
||||
<div style="display: flex; align-items: center; gap: 6px;">
|
||||
<a-input v-model:value="form.secUserId" style="flex: 1;" />
|
||||
<a-tooltip title="同步“我喜欢的”视频时,必填!!!">
|
||||
@@ -286,11 +289,11 @@ const rowCount = 4;
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="博主信息" name="upSecUserIdsJson">
|
||||
<a-form-item label="博主配置" name="upSecUserIdsJson">
|
||||
<a-form-item-rest> <!-- 用这个包裹所有辅助元素 -->
|
||||
<!-- 添加行按钮 -->
|
||||
<a-button type="primary" @click="addRow" style="margin-bottom: 12px">
|
||||
添加抖音Up主
|
||||
添加
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
@@ -321,10 +324,19 @@ const rowCount = 4;
|
||||
</div>
|
||||
</a-form-item-rest>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" name="status">
|
||||
<a-select style="width: 90px" v-model:value="form.status" :options="[
|
||||
{ label: '关闭', value: 0 },
|
||||
{ label: '开启', value: 1 },
|
||||
<a-form-item label="图文视频路径" name="imgSavePath">
|
||||
<div style="display: flex; align-items: center; gap: 6px;">
|
||||
<a-input v-model:value="form.imgSavePath" style="flex: 1;" />
|
||||
<a-tooltip title="同步图文视频必填!!!">
|
||||
<ExclamationCircleOutlined style="color: #faad14;font-size: 16px;" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="是否启用" name="status">
|
||||
<a-select style="width: 110px;" v-model:value="form.status" :options="[
|
||||
{ label: '停止同步', value: 0 },
|
||||
{ label: '开启同步', value: 1 },
|
||||
]" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
@@ -397,7 +409,7 @@ const rowCount = 4;
|
||||
</a-modal>
|
||||
|
||||
<!-- Modal弹窗 -->
|
||||
<a-modal title="要同步的Up主信息" :visible="showUpersModal" style="width:1200px;" @cancel="showUpersModal = false" @ok="showUpersModal = false">
|
||||
<a-modal title="要同步的博主信息" :visible="showUpersModal" style="width:1200px;" @cancel="showUpersModal = false" @ok="showUpersModal = false">
|
||||
<!-- 弹窗内容 -->
|
||||
<div class="cookie-content">
|
||||
{{ showUpersData || '未设置' }}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="login-page min-h-screen flex items-center justify-center p-4 md:p-8">
|
||||
<ThemeProvider :color="{
|
||||
middle: { 'bg-base': '#f8fafc' },
|
||||
middle: { 'bg-base': '#fff' },
|
||||
primary: { DEFAULT: '#4f46e5', hover: '#4338ca' }
|
||||
}">
|
||||
<!-- 登录卡片 -->
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mb-4">
|
||||
<img src="/public/logo.png">
|
||||
</div>
|
||||
<h2 class="third-title text-2xl font-bold text-gray-800 tracking-tight">抖音同步小帮手</h2>
|
||||
<h2 class="third-title text-2xl font-bold text-gray-800 tracking-tight">抖小云</h2>
|
||||
</div>
|
||||
|
||||
<a-form :model="form" :wrapperCol="{ span: 24 }" @finish="login" class="login-form w-full text-gray-700">
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
<template>
|
||||
<a-drawer v-model:visible="visible" class="custom-class" title="邮件配置" placement="right" @after-visible-change="afterVisibleChange" :maskClosable="true">
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="1">
|
||||
<template #tab>
|
||||
<span>
|
||||
<!-- <BellOutlined /> -->
|
||||
邮件STMP配置
|
||||
</span>
|
||||
</template>
|
||||
<a-form ref="emailFromRef" :model="EmailFormData" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onFinish" @finishFailed="onFinishFailed">
|
||||
<a-form-item label="是否开启" ref="Open">
|
||||
<a-checkbox v-model:checked="EmailFormData.Open">解析结果邮件通知</a-checkbox>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="EmailFormData.Open" label="Stmp地址" ref="Stmp" name="Stmp" :rules="[{ required: EmailFormData.Open, message: '请输入stmp服务地址!',validator:validateStmp }]">
|
||||
<a-input v-model:value="EmailFormData.Stmp" />
|
||||
</a-form-item>
|
||||
<a-form-item v-if="EmailFormData.Open" label="Stmp端口" ref="Port" name="Port" :rules="[{ required: EmailFormData.Open, message: '请输入stmp服务端口!',validator:validatePort }]">
|
||||
<a-input v-model:value="EmailFormData.Port" placeholder="默认465,yeah-587" />
|
||||
</a-form-item>
|
||||
<a-form-item v-if="EmailFormData.Open" label="收件Email" type='email' ref="From" name="From" :rules="[{ required: EmailFormData.Open, message: '请输入收件Email地址!',validator:validateEmail }]">
|
||||
<a-input v-model:value="EmailFormData.From" />
|
||||
<span style="color:#888">此处收件人亦是发件人</span>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="EmailFormData.Open" label="Stmp授权码" ref="Code" name="Code" :rules="[{ required: EmailFormData.Open, message: '请输入Stmp授权码!' }]">
|
||||
<a-input v-model:value="EmailFormData.Code" />
|
||||
<span style="color:#888">邮箱设置开启stmp服务会提示</span>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
|
||||
<a-button type="primary" html-type="submit">确认</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { message } from 'ant-design-vue';
|
||||
import { defineComponent, ref, onMounted, reactive, watch } from 'vue';
|
||||
import type { FormInstance } from 'ant-design-vue';
|
||||
import { useApiStore, useAccountStore } from '@/store';
|
||||
import http from '@/store/http';
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
import { checkEmail, checkDomain, checkPort } from '@/utils/regexHelper';
|
||||
const activeKey = ref<string>('1');
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
function showEmail(vis: boolean) {
|
||||
visible.value = vis;
|
||||
if (vis) loadStmpInfo();
|
||||
}
|
||||
const emailFromRef = ref<FormInstance>();
|
||||
const afterVisibleChange = (bool: boolean) => {
|
||||
if (!bool) {
|
||||
emailFromRef.value.resetFields();
|
||||
}
|
||||
};
|
||||
|
||||
const loadStmpInfo = () => {
|
||||
// useApiStore()
|
||||
// .apiGetStmpConfig()
|
||||
// .then((res) => {
|
||||
// if (res.code === 0) {
|
||||
// EmailFormData.Open = res.data.open;
|
||||
// EmailFormData.Stmp = res.data.stmp;
|
||||
// EmailFormData.Port = res.data.port;
|
||||
// EmailFormData.Code = res.data.code;
|
||||
// EmailFormData.From = res.data.from;
|
||||
// EmailFormData.Id = res.data.id;
|
||||
// }
|
||||
// });
|
||||
};
|
||||
|
||||
interface EmailFormState {
|
||||
Open: boolean;
|
||||
Stmp: string;
|
||||
Port: number;
|
||||
Code: string;
|
||||
From: string;
|
||||
Id: string;
|
||||
To: string;
|
||||
}
|
||||
const EmailFormData = reactive<EmailFormState>({
|
||||
Open: false,
|
||||
Stmp: 'smtp.qq.com',
|
||||
Port: 465,
|
||||
Code: '',
|
||||
From: '',
|
||||
Id: '',
|
||||
To: '',
|
||||
});
|
||||
const onFinish = (values: EmailFormState) => {
|
||||
EmailFormData.To = EmailFormData.From;
|
||||
// useApiStore()
|
||||
// .apiSaveStmpConfig(EmailFormData)
|
||||
// .then((res) => {
|
||||
// console.log(res);
|
||||
// if (res.code === 0) {
|
||||
// message.success('修改配置成功');
|
||||
// visible.value = false;
|
||||
// } else {
|
||||
// message.error(res.erro, 8);
|
||||
// }
|
||||
// });
|
||||
};
|
||||
|
||||
//验证邮箱
|
||||
const validateEmail = async (_rule: Rule, value: string) => {
|
||||
if (checkEmail(value)) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject('请输入正确的邮箱地址');
|
||||
}
|
||||
};
|
||||
|
||||
//验证stmp服务
|
||||
const validateStmp = async (_rule: Rule, value: string) => {
|
||||
if (checkDomain(value)) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject('请输入正确的stmp-server地址');
|
||||
}
|
||||
};
|
||||
//验证端口
|
||||
const validatePort = async (_rule: Rule, value: string) => {
|
||||
if (checkPort(value)) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject('请输入正确的端口');
|
||||
}
|
||||
};
|
||||
const onFinishFailed = (errorInfo: any) => {
|
||||
console.log('Failed:', errorInfo);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => EmailFormData.Open,
|
||||
() => {
|
||||
emailFromRef.value.validateFields(['Stmp', 'Port', 'From', 'Code']);
|
||||
},
|
||||
{ flush: 'post' }
|
||||
);
|
||||
|
||||
// 将updateMessage方法暴露给父组件调用
|
||||
defineExpose({
|
||||
showEmail,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.avatar-uploader > .ant-upload {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
.ant-upload-picture-card-wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
.ant-upload-select-picture-card i {
|
||||
font-size: 32px;
|
||||
color: #999;
|
||||
margin-top: 50px !important;
|
||||
}
|
||||
.ant-upload-select-picture-card {
|
||||
margin-top: 50px !important;
|
||||
}
|
||||
.ant-upload-select-picture-card .ant-upload-text {
|
||||
margin-top: 8px;
|
||||
color: #666;
|
||||
}
|
||||
.ant-tabs-content {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -1,4 +1,4 @@
|
||||
import MyPersonal from './MyPersonal.vue';
|
||||
import EmailSet from './EmailSet.vue';
|
||||
// import EmailSet from './EmailSet.vue';
|
||||
|
||||
export { MyPersonal, EmailSet };
|
||||
export { MyPersonal };
|
||||
|
||||
+191
-34
@@ -1,84 +1,181 @@
|
||||
<template>
|
||||
<a-card :bordered="false" :bodyStyle='{}'>
|
||||
<a-form :model="formState" :label-col="labelCol" :rules="rules" :wrapper-col="wrapperCol" ref="formRef">
|
||||
<a-card :bordered="false" :body-style="{ padding: '20px' }">
|
||||
<a-form :model="formState" :label-col="labelCol" :rules="rules" :wrapper-col="wrapperCol" ref="formRef" label-align="right">
|
||||
<!-- 任务调度配置 -->
|
||||
<div class="form-section">
|
||||
<h3 class="section-title">任务调度配置</h3>
|
||||
|
||||
<a-form-item has-feedback label="同步周期(分钟)" ref="Cron" name="Cron">
|
||||
<a-input v-model:value="formState.Cron" placeholder="" />
|
||||
<a-alert message="1:数字 20-表示20分钟执行一次;" type="success" />
|
||||
<a-alert message="2:cron表达式,根据表达式周期执行" type="success" />
|
||||
</a-form-item>
|
||||
<a-form-item label="在线Cron表达式">
|
||||
<a target="_blank" href="https://www.bejson.com/othertools/cron/">查看示例</a>
|
||||
</a-form-item>
|
||||
<a-form-item has-feedback label="同步周期(分钟)" name="Cron" :wrapper-col="{ span: 6 }">
|
||||
<a-input v-model:value="formState.Cron" placeholder="请输入数字或Cron表达式" />
|
||||
<div class="help-text">
|
||||
<p>1. 数字:表示每隔该分钟执行一次(如 20 表示每20分钟)</p>
|
||||
<p>2. Cron表达式:按表达式规则执行(如 0 0/1 * * * 表示每小时执行一次)</p>
|
||||
</div>
|
||||
<a-form-item label="在线Cron表达式" :wrapper-col="{ span: 12, offset: 0 }">
|
||||
<a target="_blank" href="https://www.bejson.com/othertools/cron/" class="cron-link">查看示例</a>
|
||||
</a-form-item>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item has-feedback label="扫描行数" ref="BatchCount" name="BatchCount">
|
||||
<a-input v-model:value="formState.BatchCount" placeholder="" />
|
||||
<a-alert message="每次扫描行数,第一次同步完成后,可以适当调高提高效率" type="success" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<a-form-item :wrapper-col="{ span: 10, offset: 3 }">
|
||||
<a-space>
|
||||
<!-- 文件保存配置 -->
|
||||
<div class="form-section">
|
||||
<h3 class="section-title">博主视频保存配置</h3>
|
||||
|
||||
<a-form-item has-feedback label="视频用标题做文件名" name="UperUseViedoTitle" :wrapper-col="{ span: 6 }">
|
||||
<a-switch v-model:checked="formState.UperUseViedoTitle" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item has-feedback label="视频不创建子文件夹" name="UperSaveTogether" :wrapper-col="{ span: 6 }">
|
||||
<a-switch v-model:checked="formState.UperSaveTogether" />
|
||||
</a-form-item>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 系统配置 -->
|
||||
<div class="form-section">
|
||||
<h3 class="section-title">系统配置</h3>
|
||||
<a-form-item has-feedback label="每次同步条数" name="BatchCount" :wrapper-col="{ span: 12 }">
|
||||
<a-input-number v-model:value="formState.BatchCount" placeholder="请输入查询条数" :min="1" :max="100" />
|
||||
</a-form-item>
|
||||
<a-form-item has-feedback label="日志保留天数" name="LogKeepDay" :wrapper-col="{ span: 12 }">
|
||||
<a-input-number v-model:value="formState.LogKeepDay" placeholder="请输入保留天数" :min="1" :max="90" />
|
||||
</a-form-item>
|
||||
<a-form-item v-if="downImgVideo" has-feedback label="同步图文视频" name="DownImageVideo" :wrapper-col="{ span: 6 }">
|
||||
<a-switch v-model:checked="formState.DownImageVideo" />
|
||||
</a-form-item>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<a-form-item :wrapper-col="{ span: 12, offset: 5 }" class="form-actions">
|
||||
<a-space size="middle">
|
||||
<a-button type="primary" @click="onUpdate" v-if="componentDisabled">修改配置</a-button>
|
||||
<a-button type="primary" danger @click="onSubmit" v-if="!componentDisabled">确认</a-button>
|
||||
<a-button type="primary" danger @click="onSubmit" v-if="!componentDisabled">确认保存</a-button>
|
||||
<a-button type="default" @click="onCancel" v-if="!componentDisabled">取消</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, toRaw, ref, watch } from 'vue';
|
||||
import { reactive, toRaw, ref, watch, onMounted } from 'vue';
|
||||
import type { UnwrapRef } from 'vue';
|
||||
import { Form } from 'ant-design-vue';
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
import type { FormInstance } from 'ant-design-vue';
|
||||
import { useApiStore } from '@/store';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { onMounted } from 'vue';
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
// 状态定义
|
||||
const componentDisabled = ref(true);
|
||||
const SK = ref(null);
|
||||
const downImgVideo = ref(false);
|
||||
|
||||
// 表单数据结构
|
||||
interface FormState {
|
||||
Cron: string;
|
||||
Id: string;
|
||||
BatchCount: number;
|
||||
DownImageVideoFromEnv: boolean;
|
||||
DownImageVideo: boolean;
|
||||
UperSaveTogether: boolean;
|
||||
UperUseViedoTitle: boolean;
|
||||
LogKeepDay: number;
|
||||
}
|
||||
|
||||
// 表单初始数据
|
||||
const formState: UnwrapRef<FormState> = reactive({
|
||||
Cron: '30',
|
||||
Id: '0',
|
||||
BatchCount: 10,
|
||||
LogKeepDay: 10,
|
||||
UperUseViedoTitle: false,
|
||||
UperSaveTogether: false,
|
||||
DownImageVideo: false,
|
||||
DownImageVideoFromEnv: false,
|
||||
});
|
||||
|
||||
// 表单校验规则
|
||||
const rules: Record<string, Rule[]> = {
|
||||
Cron: [{ required: true, message: '请输入任务调度周期', trigger: 'change' }],
|
||||
Cron: [
|
||||
{ required: true, message: '请输入任务调度周期', trigger: 'change' },
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
// 验证数字或cron表达式
|
||||
if (/^\d+$/.test(value) || /^(\d+\s*){5,6}$/.test(value)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error('请输入有效的数字或Cron表达式'));
|
||||
},
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
BatchCount: [
|
||||
{ required: true, message: '请输入每次查询条数', trigger: 'change' },
|
||||
{ type: 'number', min: 1, max: 100, message: '查询条数应在1-100之间', trigger: 'change' },
|
||||
],
|
||||
LogKeepDay: [
|
||||
{ required: true, message: '请输入日志保留天数', trigger: 'change' },
|
||||
{ type: 'number', min: 1, max: 90, message: '保留天数应在1-90之间', trigger: 'change' },
|
||||
],
|
||||
};
|
||||
|
||||
// 布局配置
|
||||
const labelCol = { style: { width: '150px', textAlign: 'right' } };
|
||||
const wrapperCol = { span: 12 };
|
||||
|
||||
// 获取配置数据
|
||||
const getConfig = () => {
|
||||
useApiStore()
|
||||
.apiGetConfig()
|
||||
.then((res) => {
|
||||
if (res.code === 0) {
|
||||
formState.Cron = res.data.cron;
|
||||
formState.Id = res.data.id;
|
||||
formState.BatchCount = res.data.batchCount;
|
||||
// 使用Object.assign避免重复赋值
|
||||
Object.assign(formState, {
|
||||
Cron: res.data.cron,
|
||||
Id: res.data.id,
|
||||
BatchCount: res.data.batchCount,
|
||||
DownImageVideoFromEnv: res.data.downImageVideoFromEnv,
|
||||
DownImageVideo: res.data.downImageVideo,
|
||||
UperUseViedoTitle: res.data.uperUseViedoTitle,
|
||||
UperSaveTogether: res.data.uperSaveTogether,
|
||||
LogKeepDay: res.data.logKeepDay,
|
||||
});
|
||||
|
||||
// 监听环境变量配置,控制是否显示图片视频下载选项
|
||||
downImgVideo.value = res.data.downImageVideoFromEnv;
|
||||
} else {
|
||||
message.error(res.erro, 8);
|
||||
message.error(res.erro || '获取配置失败', 8);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('获取配置失败:', error);
|
||||
message.error('获取配置失败,请稍后重试', 8);
|
||||
});
|
||||
};
|
||||
|
||||
// 组件挂载时获取配置
|
||||
onMounted(() => {
|
||||
getConfig();
|
||||
});
|
||||
|
||||
//提交初始化
|
||||
// // 监听环境变量变化
|
||||
// watch(
|
||||
// () => formState.DownImageVideoFromEnv,
|
||||
// (value) => {
|
||||
// downImgVideo.value = value;
|
||||
// }
|
||||
// );
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = () => {
|
||||
// console.log('submit!', toRaw(formState));
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
console.log('values', formState, toRaw(formState));
|
||||
useApiStore()
|
||||
.apiUpdateConfig(toRaw(formState))
|
||||
.then((res) => {
|
||||
@@ -86,27 +183,87 @@ const onSubmit = () => {
|
||||
message.success('配置修改生效,同步任务将按照新的规则执行');
|
||||
componentDisabled.value = true;
|
||||
} else {
|
||||
message.error(res.erro, 8);
|
||||
message.error(res.erro || '更新配置失败', 8);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('更新配置失败:', error);
|
||||
message.error('更新配置失败,请稍后重试', 8);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('error', error);
|
||||
console.log('表单校验失败:', error);
|
||||
message.error('请检查表单填写是否正确', 8);
|
||||
});
|
||||
};
|
||||
|
||||
//修改配置开启
|
||||
// 进入编辑模式
|
||||
const onUpdate = () => {
|
||||
componentDisabled.value = false;
|
||||
};
|
||||
|
||||
// 取消编辑
|
||||
const onCancel = () => {
|
||||
componentDisabled.value = true;
|
||||
// 可以考虑添加重置表单的逻辑
|
||||
formRef.value?.clearValidate();
|
||||
};
|
||||
const labelCol = { style: { width: '150px' } };
|
||||
const wrapperCol = { span: 4 };
|
||||
</script>
|
||||
|
||||
<style lang='less' scoped>
|
||||
@primary-color: #1890ff;
|
||||
@text-color: #333;
|
||||
@border-radius: 4px;
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: @primary-color;
|
||||
margin-bottom: 15px;
|
||||
padding-left: 5px;
|
||||
border-left: 3px solid @primary-color;
|
||||
}
|
||||
|
||||
.help-text {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
|
||||
p {
|
||||
margin: 4px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.cron-link {
|
||||
color: @primary-color;
|
||||
text-decoration: underline;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: #096dd9;
|
||||
}
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.ant-radio-button-wrapper-disabled.ant-radio-button-wrapper-checked {
|
||||
color: rgb(164 158 158) !important;
|
||||
background-color: #e6e6e6 !important;
|
||||
|
||||
@@ -13,12 +13,13 @@
|
||||
<a-radio-button value="1">喜欢的</a-radio-button>
|
||||
<a-radio-button value="2">收藏的</a-radio-button>
|
||||
<a-radio-button value="3">关注的</a-radio-button>
|
||||
<a-radio-button value="4">图文视频</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="GetRecords">查询</a-button>
|
||||
<a-button type="danger" @click="StartNow">立即重新同步</a-button>
|
||||
<a-button type="danger" @click="StartNow">立即同步</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
@@ -52,28 +53,29 @@ const columns = ref([
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '视频类型',
|
||||
dataIndex: 'viedoCate',
|
||||
// sorter: true,
|
||||
width: 300,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '作者',
|
||||
title: '博主',
|
||||
dataIndex: 'author',
|
||||
// sorter: true,
|
||||
align: 'center',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '视频名称',
|
||||
title: '视频类型',
|
||||
dataIndex: 'viedoCate',
|
||||
// sorter: true,
|
||||
width: 300,
|
||||
align: 'center',
|
||||
},
|
||||
|
||||
{
|
||||
title: '视频标题',
|
||||
dataIndex: 'videoTitle',
|
||||
// sorter: true,
|
||||
align: 'left',
|
||||
},
|
||||
|
||||
{
|
||||
title: '用户',
|
||||
title: 'Cookie',
|
||||
dataIndex: 'dyUser',
|
||||
// sorter: true,
|
||||
align: 'center',
|
||||
|
||||
+361
-290
@@ -1,126 +1,141 @@
|
||||
<template>
|
||||
<div class="stats-dashboard">
|
||||
<div class="dashboard-container">
|
||||
<!-- 核心统计概览 - 平分宽度 -->
|
||||
<!-- 核心统计概览 - 美化版 -->
|
||||
<section class="stats-overview">
|
||||
<div class="stat-card primary-card">
|
||||
<!-- 总视频数卡片 -->
|
||||
<div class="stat-card primary-card main-card">
|
||||
<!-- 卡片头部 -->
|
||||
<div class="stat-header">
|
||||
<span class="stat-meta">总视频数</span>
|
||||
<div class="header-left">
|
||||
<span class="stat-meta">视频总数</span>
|
||||
<div class="stat-value">{{ totalVideos }}</div>
|
||||
</div>
|
||||
<div class="stat-icon video-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polygon points="23 7 16 12 23 17 23 7"></polygon>
|
||||
<rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-value">{{ totalVideos }}</div>
|
||||
<div class="stat-trend"></div>
|
||||
|
||||
<!-- 细分项区域 -->
|
||||
<div class="stat-subitems">
|
||||
<div class="subitem" :title="`我喜欢的视频数: ${favoriteCount}`">
|
||||
<div class="subitem-icon like-icon">
|
||||
<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">
|
||||
<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>
|
||||
<span class="subitem-value">{{ favoriteCount }}</span>
|
||||
</div>
|
||||
|
||||
<div class="subitem" :title="`我收藏的视频数: ${collectCount}`">
|
||||
<div class="subitem-icon collect-icon">
|
||||
<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">
|
||||
<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>
|
||||
<span class="subitem-value">{{ collectCount }}</span>
|
||||
</div>
|
||||
|
||||
<div class="subitem" :title="`我关注的视频数: ${followCount}`">
|
||||
<div class="subitem-icon follow-icon">
|
||||
<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">
|
||||
<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>
|
||||
<span class="subitem-value">{{ followCount }}</span>
|
||||
</div>
|
||||
|
||||
<div class="subitem" :title="`图文视频数: ${graphicVideoCount}`">
|
||||
<div class="subitem-icon graphic-icon">
|
||||
<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">
|
||||
<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>
|
||||
<span class="subitem-value">{{ graphicVideoCount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card secondary-card">
|
||||
<!-- 总占用空间卡片 -->
|
||||
<div class="stat-card secondary-card main-card">
|
||||
<!-- 卡片头部 -->
|
||||
<div class="stat-header">
|
||||
<span class="stat-meta">总占用空间</span>
|
||||
<div class="header-left">
|
||||
<span class="stat-meta">空间总计</span>
|
||||
<div class="stat-value">{{ fileSizeTotal }} <span class="unit">G</span></div>
|
||||
</div>
|
||||
<div class="stat-icon size-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M22 12H2v8h20v-8z" />
|
||||
<path d="M6 18h.01" />
|
||||
<path d="M10 18h.01" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-value">{{ fileSizeTotal }} G</div>
|
||||
<div class="stat-trend"></div>
|
||||
|
||||
<!-- 细分项区域 -->
|
||||
<div class="stat-subitems">
|
||||
<div class="subitem" :title="`喜欢的视频占用: ${favoriteSize}G`">
|
||||
<div class="subitem-icon like-icon">
|
||||
<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">
|
||||
<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>
|
||||
<span class="subitem-value">{{ favoriteSize }} <span class="unit">G</span></span>
|
||||
</div>
|
||||
|
||||
<div class="subitem" :title="`收藏的视频占用: ${collectSize}G`">
|
||||
<div class="subitem-icon collect-icon">
|
||||
<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">
|
||||
<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>
|
||||
<span class="subitem-value">{{ collectSize }} <span class="unit">G</span></span>
|
||||
</div>
|
||||
|
||||
<div class="subitem" :title="`关注的视频占用: ${followSize}G`">
|
||||
<div class="subitem-icon follow-icon">
|
||||
<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">
|
||||
<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>
|
||||
<span class="subitem-value">{{ followSize }} <span class="unit">G</span></span>
|
||||
</div>
|
||||
|
||||
<div class="subitem" :title="`图文视频占用: ${graphicVideoSize}G`">
|
||||
<div class="subitem-icon graphic-icon">
|
||||
<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">
|
||||
<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>
|
||||
<span class="subitem-value">{{ graphicVideoSize }} <span class="unit">G</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 次级统计卡片组 - 包含喜欢、收藏、关注的数量及空间统计 -->
|
||||
<section class="stats-grid">
|
||||
<!-- 我喜欢的数量卡片 -->
|
||||
<div class="stat-card mini-card">
|
||||
<div class="stat-header">
|
||||
<span class="stat-meta">我喜欢的</span>
|
||||
<div class="stat-icon like-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" 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>
|
||||
</div>
|
||||
<div class="stat-value">{{ favoriteCount }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 我收藏的数量卡片 -->
|
||||
<div class="stat-card mini-card">
|
||||
<div class="stat-header">
|
||||
<span class="stat-meta">我收藏的</span>
|
||||
<div class="stat-icon collect-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" 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>
|
||||
</div>
|
||||
<div class="stat-value">{{ collectCount }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 我关注的数量卡片 -->
|
||||
<div class="stat-card mini-card">
|
||||
<div class="stat-header">
|
||||
<span class="stat-meta">我关注的</span>
|
||||
<div class="stat-icon follow-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" 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>
|
||||
</div>
|
||||
<div class="stat-value">{{ followCount }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 喜欢占用空间卡片 -->
|
||||
<div class="stat-card mini-card">
|
||||
<div class="stat-header">
|
||||
<span class="stat-meta">喜欢占用空间</span>
|
||||
<div class="stat-icon size-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" 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>
|
||||
</div>
|
||||
<div class="stat-value">{{ favoriteSize }} G</div>
|
||||
</div>
|
||||
|
||||
<!-- 收藏占用空间卡片 -->
|
||||
<div class="stat-card mini-card">
|
||||
<div class="stat-header">
|
||||
<span class="stat-meta">收藏占用空间</span>
|
||||
<div class="stat-icon size-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" 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>
|
||||
</div>
|
||||
<div class="stat-value">{{ collectSize }} G</div>
|
||||
</div>
|
||||
|
||||
<!-- 关注占用空间卡片 -->
|
||||
<div class="stat-card mini-card">
|
||||
<div class="stat-header">
|
||||
<span class="stat-meta">关注占用空间</span>
|
||||
<div class="stat-icon size-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" 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>
|
||||
</div>
|
||||
<div class="stat-value">{{ followSize }} G</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 详细分类统计 -->
|
||||
<!-- 详细分类统计(保持不变) -->
|
||||
<section class="detailed-stats">
|
||||
<div class="stats-header">
|
||||
<div class="tab-controls">
|
||||
@@ -184,7 +199,6 @@
|
||||
<symbol id="cup" viewBox="0 0 12 12">
|
||||
<path d="M18 4h2v16h-2zM4 4h14v2H4zM4 8h10v2H4zM4 12h10v2H4zM4 16h6v2H4zM4 20h6v2H4z" />
|
||||
</symbol>
|
||||
<!-- 其他图标省略,保持原样 -->
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
@@ -207,7 +221,7 @@ interface Category {
|
||||
icon: string;
|
||||
}
|
||||
|
||||
// 状态管理 - 包含喜欢、收藏、关注的数量及空间统计
|
||||
// 状态管理
|
||||
const totalVideos = ref<number>(0);
|
||||
const totalAuthors = ref<number>(0);
|
||||
const categoryTotal = ref<number>(0);
|
||||
@@ -216,24 +230,26 @@ const totalDiskSize = ref<string>('0.00');
|
||||
|
||||
const favoriteCount = ref<number>(0);
|
||||
const collectCount = ref<number>(0);
|
||||
const followCount = ref<number>(0); // 新增:关注的数量
|
||||
const favoriteSize = ref<string>('0.00'); // 喜欢占用空间
|
||||
const collectSize = ref<string>('0.00'); // 收藏占用空间
|
||||
const followSize = ref<string>('0.00'); // 新增:关注占用空间
|
||||
const followCount = ref<number>(0);
|
||||
const graphicVideoCount = 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 categories = ref<Category[]>([]);
|
||||
const authors = ref<Author[]>([]);
|
||||
const currentTab = ref<string>('author');
|
||||
const tabCount = ref<number>(0); // tab切换时显示数量
|
||||
const tabCount = ref<number>(0);
|
||||
|
||||
// 组件名称
|
||||
defineOptions({
|
||||
name: 'StatsDashboard',
|
||||
});
|
||||
|
||||
// 加载数据
|
||||
// 加载数据和切换标签逻辑
|
||||
onMounted(() => {
|
||||
// const html = document.documentElement;
|
||||
// html.classList.add('dark-mode');
|
||||
loadDashboardData();
|
||||
});
|
||||
|
||||
@@ -245,6 +261,7 @@ const changeTab = (e: any) => {
|
||||
tabCount.value = categoryTotal.value;
|
||||
}
|
||||
};
|
||||
|
||||
const loadDashboardData = async () => {
|
||||
try {
|
||||
const res = await useApiStore().VideoStatics();
|
||||
@@ -253,16 +270,20 @@ const loadDashboardData = async () => {
|
||||
totalVideos.value = res.data.videoCount;
|
||||
fileSizeTotal.value = res.data.videoSizeTotal || 0.0;
|
||||
totalDiskSize.value = res.data.totalDiskSize || 0.0;
|
||||
|
||||
favoriteCount.value = res.data.favoriteCount;
|
||||
collectCount.value = res.data.collectCount;
|
||||
followCount.value = res.data.followCount || 0; // 新增:读取关注数量
|
||||
followCount.value = res.data.followCount || 0;
|
||||
graphicVideoCount.value = res.data.graphicVideoCount || 0;
|
||||
|
||||
favoriteSize.value = res.data.videoFavoriteSize || 0.0;
|
||||
collectSize.value = res.data.videoCollectSize || 0.0;
|
||||
followSize.value = res.data.videoFollowSize || 0.0; // 新增:读取关注占用空间
|
||||
followSize.value = res.data.videoFollowSize || 0.0;
|
||||
graphicVideoSize.value = res.data.graphicVideoSize || 0.0;
|
||||
|
||||
categories.value = res.data.categories;
|
||||
authors.value = res.data.authors;
|
||||
|
||||
// 分类图标和颜色随机分配
|
||||
const cates = getRandomElements(categoriessss.value, categories.value.length);
|
||||
const colors = getRandomElements(colorArray, categories.value.length);
|
||||
categories.value.forEach((item, index) => {
|
||||
@@ -274,14 +295,12 @@ const loadDashboardData = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 随机获取数组元素方法
|
||||
const getRandomElements = (arr: any[], n: number) => {
|
||||
if (n <= 0) return [];
|
||||
if (n >= arr.length) return [...arr];
|
||||
return [...arr].sort(() => Math.random() - 0.5).slice(0, n);
|
||||
};
|
||||
|
||||
// 图标列表和颜色数组
|
||||
const categoriessss = ref<any[]>([
|
||||
'cup',
|
||||
'spoon',
|
||||
@@ -322,7 +341,7 @@ const colorArray = [
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 白天模式默认样式 */
|
||||
/* 基础样式 */
|
||||
.stats-dashboard {
|
||||
min-height: 100vh;
|
||||
background-color: #ffffff;
|
||||
@@ -343,115 +362,191 @@ const colorArray = [
|
||||
}
|
||||
}
|
||||
|
||||
/* 概览区域布局 - 始终平分宽度 */
|
||||
/* 核心概览区域 */
|
||||
.stats-overview {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr; /* 1:1平分宽度 */
|
||||
gap: 20px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 25px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
/* 次级统计网格布局 - 适配6个卡片(喜欢、收藏、关注的数量+空间) */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 15px;
|
||||
margin-bottom: 30px;
|
||||
/* 主卡片样式 - 增强边框可见性 */
|
||||
.main-card {
|
||||
padding: 28px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
/* 白天模式添加明显边框 */
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(3, 1fr); /* 中等屏幕每行3个 */
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(3, 1fr); /* 平板屏幕每行3个 */
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(6, 1fr); /* 大屏幕每行6个,均匀分布 */
|
||||
}
|
||||
}
|
||||
|
||||
/* 其他样式保持不变 */
|
||||
.stat-card {
|
||||
background: #f5f5f5;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15);
|
||||
/* 卡片hover效果 */
|
||||
.main-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 12px 35px rgba(0, 0, 0, 0.12);
|
||||
/* hover时边框颜色加深 */
|
||||
border-color: #d0d0d0;
|
||||
}
|
||||
|
||||
/* 卡片头部 */
|
||||
.stat-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
/* 卡片元数据 */
|
||||
.stat-meta {
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
color: #666666;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
letter-spacing: 0.6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 卡片主数值 */
|
||||
.stat-value {
|
||||
font-size: 42px;
|
||||
font-weight: 700;
|
||||
color: #1a1a1a;
|
||||
line-height: 1.1;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 单位样式 */
|
||||
.unit {
|
||||
font-size: 22px;
|
||||
color: #444444;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 卡片图标 */
|
||||
.stat-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.video-icon {
|
||||
background-color: rgba(76, 175, 80, 0.2);
|
||||
flex-shrink: 0;
|
||||
background-color: rgba(76, 175, 80, 0.15);
|
||||
color: #4caf50;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.size-icon {
|
||||
background-color: rgba(33, 150, 243, 0.2);
|
||||
/* 空间卡片图标颜色 */
|
||||
.secondary-card .stat-icon {
|
||||
background-color: rgba(33, 150, 243, 0.15);
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.like-icon {
|
||||
background-color: rgba(233, 30, 99, 0.2);
|
||||
/* 卡片hover时图标缩放 */
|
||||
.main-card:hover .stat-icon {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
/* 细分项容器 - 增强分隔线 */
|
||||
.stat-subitems {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 15px;
|
||||
padding-top: 20px;
|
||||
/* 白天模式使用明显的分隔线 */
|
||||
border-top: 1px solid #d0d0d0;
|
||||
}
|
||||
|
||||
/* 细分项样式 - 增强边框和背景 */
|
||||
.subitem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px 16px;
|
||||
/* 白天模式添加白色背景和明显边框 */
|
||||
background: #ffffff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.04);
|
||||
transition: all 0.2s ease;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* 细分项hover效果 - 增强边框和阴影 */
|
||||
.subitem:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.07);
|
||||
border-color: #c0c0c0;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
/* 细分项图标 */
|
||||
.subitem-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
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.2);
|
||||
background-color: rgba(255, 152, 0, 0.1);
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
/* 新增:关注图标样式 */
|
||||
/* 关注图标颜色 */
|
||||
.follow-icon {
|
||||
background-color: rgba(156, 39, 176, 0.2);
|
||||
background-color: rgba(156, 39, 176, 0.1);
|
||||
color: #9c27b0;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 10px 0;
|
||||
color: #333333;
|
||||
line-height: 1;
|
||||
/* 图文图标颜色 */
|
||||
.graphic-icon {
|
||||
background-color: rgba(255, 159, 64, 0.1);
|
||||
color: #d9091a;
|
||||
}
|
||||
|
||||
.mini-card .stat-value {
|
||||
font-size: 24px;
|
||||
/* 细分项元数据 */
|
||||
.subitem-meta {
|
||||
font-size: 13px;
|
||||
color: #666666;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.3px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 细分项数值 */
|
||||
.subitem-value {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #222222;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* 细分项单位 */
|
||||
.subitem-value .unit {
|
||||
font-size: 12px;
|
||||
color: #555555;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 卡片顶部主题边框 */
|
||||
.primary-card {
|
||||
border-top: 4px solid #4caf50;
|
||||
}
|
||||
@@ -460,7 +555,7 @@ const colorArray = [
|
||||
border-top: 4px solid #2196f3;
|
||||
}
|
||||
|
||||
/* 详细统计区域 */
|
||||
/* 详细分类统计区域 */
|
||||
.detailed-stats {
|
||||
background: #f5f5f5;
|
||||
border-radius: 12px;
|
||||
@@ -475,12 +570,6 @@ const colorArray = [
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.stats-header h2 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.tab-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
@@ -503,12 +592,6 @@ const colorArray = [
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tab-btn:hover:not(.active) {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
/* 统计内容区域 */
|
||||
.stats-content {
|
||||
animation: fadeIn 0.5s ease;
|
||||
}
|
||||
@@ -534,19 +617,23 @@ const colorArray = [
|
||||
}
|
||||
}
|
||||
|
||||
/* 作者卡片 */
|
||||
.author-card {
|
||||
.author-card,
|
||||
.category-card {
|
||||
background: #eeeeee;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.author-card:hover {
|
||||
.author-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.author-card:hover,
|
||||
.category-card:hover {
|
||||
transform: translateX(5px);
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
@@ -564,17 +651,15 @@ const colorArray = [
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.author-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.author-name {
|
||||
.author-name,
|
||||
.category-name {
|
||||
margin: 0 0 5px 0;
|
||||
font-size: 16px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.author-stats {
|
||||
.author-stats,
|
||||
.category-stats {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: #666666;
|
||||
@@ -594,21 +679,10 @@ const colorArray = [
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
/* 分类卡片 */
|
||||
.category-card {
|
||||
background: #eeeeee;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.category-card:hover {
|
||||
transform: translateX(5px);
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.category-icon {
|
||||
@@ -622,29 +696,12 @@ const colorArray = [
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.category-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.category-name {
|
||||
margin: 0 0 3px 0;
|
||||
font-size: 16px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.category-stats {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.category-percentage {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--category-color);
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
@@ -667,33 +724,40 @@ const colorArray = [
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
/* 晚上模式样式 */
|
||||
/* 夜间模式样式 - 保持原有效果 */
|
||||
html.dark-mode .main-card {
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
background-color: rgba(30, 30, 50, 0.9);
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
html.dark-mode .main-card:hover {
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
box-shadow: 0 12px 35px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
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);
|
||||
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
html.dark-mode .subitem:hover {
|
||||
background: rgba(40, 40, 65, 0.9);
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* 夜间模式其他样式保持不变 */
|
||||
html.dark-mode .stats-dashboard {
|
||||
background-color: #1a1a2e;
|
||||
color: #eaeaea;
|
||||
}
|
||||
|
||||
html.dark-mode .dashboard-header {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
html.dark-mode .dashboard-header h1 {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
html.dark-mode .header-subtitle {
|
||||
color: #b0b0c3;
|
||||
}
|
||||
|
||||
html.dark-mode .stat-card {
|
||||
background: rgba(30, 30, 50, 0.8);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
html.dark-mode .stat-card:hover {
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
html.dark-mode .stat-meta {
|
||||
color: #b0b0c3;
|
||||
}
|
||||
@@ -702,51 +766,58 @@ html.dark-mode .stat-value {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
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-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 .detailed-stats {
|
||||
background: rgba(30, 30, 50, 0.8);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
html.dark-mode .stats-header h2 {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
html.dark-mode .tab-btn {
|
||||
color: #b0b0c3;
|
||||
}
|
||||
|
||||
html.dark-mode .tab-btn:hover:not(.active) {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
html.dark-mode .author-card {
|
||||
background: rgba(40, 40, 65, 0.6);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
html.dark-mode .author-name {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
html.dark-mode .author-stats {
|
||||
color: #b0b0c3;
|
||||
}
|
||||
|
||||
html.dark-mode .author-progress {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
html.dark-mode .author-card,
|
||||
html.dark-mode .category-card {
|
||||
background: rgba(40, 40, 65, 0.6);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
html.dark-mode .author-name,
|
||||
html.dark-mode .category-name {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
html.dark-mode .category-stats {
|
||||
color: #b0b0c3;
|
||||
}
|
||||
</style>
|
||||
@@ -4,7 +4,7 @@ import { ref } from 'vue';
|
||||
export type Navigation = 'side' | 'head' | 'mix';
|
||||
|
||||
export const useSettingStore = defineStore('setting', () => {
|
||||
const navigation = ref<Navigation>('head');
|
||||
const navigation = ref<Navigation>('side');
|
||||
const useTabs = ref<boolean>(true);
|
||||
const theme = ref('header-night');
|
||||
const contentClass = ref('common');
|
||||
|
||||
@@ -21,13 +21,13 @@ export function configTheme(key: string) {
|
||||
setBgSeriesColors({ 'bg-base': '#1a1a2e' });
|
||||
break;
|
||||
case 'header-dark':
|
||||
setNavigation('head');
|
||||
// setNavigation('head');
|
||||
break;
|
||||
default:
|
||||
setNavigation('side');
|
||||
// setNavigation('side');
|
||||
}
|
||||
if (key.indexOf('header') !== -1) {
|
||||
setNavigation('head');
|
||||
// setNavigation('head');
|
||||
setTheme(key.split('-')[1])
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"dbconn": "",
|
||||
"dbtype": "Sqlite",
|
||||
"DOWN_IMGVIDEO": "1",
|
||||
"DOWN_IMGVIDEO": "1"
|
||||
}
|
||||
@@ -35,6 +35,10 @@
|
||||
public long FollowCount { get; set; }
|
||||
|
||||
|
||||
public long GraphicVideoCount { get; set; }
|
||||
public string GraphicVideoSize { get; set; }
|
||||
|
||||
|
||||
public string TotalDiskSize { get; set; }
|
||||
public List<VideoStaticsItemDto> Authors { get; set; } = new List<VideoStaticsItemDto>();
|
||||
public List<VideoStaticsItemDto> Categories { get; set; } = new List<VideoStaticsItemDto>();
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
namespace dy.net.dto
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace dy.net.dto
|
||||
{
|
||||
public enum VideoTypeEnum
|
||||
{
|
||||
|
||||
[Description("喜欢的")]
|
||||
Favorite = 1,
|
||||
[Description("收藏的")]
|
||||
Collect = 2,
|
||||
[Description("关注的")]
|
||||
UperPost = 3,
|
||||
[Description("图片视频")]
|
||||
ImageVideo = 4
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using dy.net.dto;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
|
||||
namespace dy.net.extension
|
||||
{
|
||||
public static class EnumExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取枚举值的 Description 特性描述
|
||||
/// </summary>
|
||||
/// <param name="enumValue">枚举值</param>
|
||||
/// <returns>如果存在 Description 特性,则返回其描述;否则返回枚举值的名称</returns>
|
||||
public static string GetDescription(this Enum enumValue)
|
||||
{
|
||||
// 1. 获取枚举值对应的类型
|
||||
Type enumType = enumValue.GetType();
|
||||
|
||||
// 2. 获取枚举值对应的成员信息
|
||||
MemberInfo[] memberInfo = enumType.GetMember(enumValue.ToString());
|
||||
|
||||
if (memberInfo.Length > 0)
|
||||
{
|
||||
// 3. 检查该成员是否有 Description 特性
|
||||
object[] attributes = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
|
||||
|
||||
if (attributes.Length > 0)
|
||||
{
|
||||
// 4. 如果有,返回 Description 的值
|
||||
return ((DescriptionAttribute)attributes[0]).Description;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 如果没有找到 Description 特性,返回枚举值本身的名称
|
||||
return enumValue.GetHashCode().ToString();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 将 int 值转换为 VideoTypeEnum。
|
||||
/// </summary>
|
||||
/// <param name="value">要转换的 int 值</param>
|
||||
/// <returns>转换成功则返回对应的 VideoTypeEnum 值,否则返回 null</returns>
|
||||
public static VideoTypeEnum? ToVideoTypeEnum(this int value)
|
||||
{
|
||||
if (Enum.IsDefined(typeof(VideoTypeEnum), value))
|
||||
{
|
||||
return (VideoTypeEnum)value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static VideoTypeEnum? ToVideoTypeEnum(this string value)
|
||||
{
|
||||
if(int.TryParse(value, out int intValue))
|
||||
{
|
||||
return ToVideoTypeEnum(intValue);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using dy.net.model;
|
||||
using dy.net.service;
|
||||
using dy.net.utils;
|
||||
using System;
|
||||
|
||||
namespace dy.net.job
|
||||
{
|
||||
@@ -22,6 +23,8 @@ namespace dy.net.job
|
||||
if (now.Hour == 1 && now.Minute < 30)
|
||||
{
|
||||
_commonService.UpdateAllCookieSyncedToZero();
|
||||
//顺手清理下日志文件
|
||||
LogFileCleaner.CleanOldLogFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs"), 1);
|
||||
await Task.Delay(200);
|
||||
}
|
||||
}
|
||||
|
||||
+12
-11
@@ -533,7 +533,7 @@ namespace dy.net.job
|
||||
// 特殊处理合成视频的字段
|
||||
videoEntity.FileHash = string.Empty; // 合成视频没有原始文件哈希
|
||||
videoEntity.VideoUrl = "/"; // 合成视频没有原始URL
|
||||
videoEntity.ViedoType = VideoTypeEnum.ImageVideo.ToString(); // 标记为图片合成视频
|
||||
videoEntity.ViedoType = VideoTypeEnum.ImageVideo; // 标记为图片合成视频
|
||||
|
||||
return videoEntity;
|
||||
}
|
||||
@@ -586,6 +586,7 @@ namespace dy.net.job
|
||||
{
|
||||
(string tag1, string tag2, string tag3) = GetVideoTags(item);
|
||||
var nfoFileName = GetNfoFileName(cookie, item, config, ".nfo");
|
||||
var poster = GetNfoFileName(cookie, item, config, "poster.jpg");
|
||||
var nfoPath = Path.Combine(saveFolder, nfoFileName);
|
||||
NfoFileGenerator.GenerateNfoFile(new DouyinVideoNfo
|
||||
{
|
||||
@@ -598,9 +599,9 @@ namespace dy.net.job
|
||||
}
|
||||
},
|
||||
Author = item.Author?.Nickname,
|
||||
Poster = GetNfoFileName(cookie, item, config, "poster.jpg"),
|
||||
Poster = poster,
|
||||
Title = item.Desc,
|
||||
Thumbnail = GetNfoFileName(cookie, item, config, "fanart.jpg"),
|
||||
Thumbnail = poster,// 使用poster作为缩略图
|
||||
ReleaseDate = DateTimeUtil.Convert10BitTimestamp(item.CreateTime),
|
||||
Genres = new List<string> { tag1, tag2, tag3 }.Where(t => !string.IsNullOrWhiteSpace(t)).ToList()
|
||||
}, nfoPath);
|
||||
@@ -748,13 +749,13 @@ namespace dy.net.job
|
||||
if (!File.Exists(coverSavePath))
|
||||
{
|
||||
var downRes = await _douyinService.DownloadAsync(coverUrl, coverSavePath, cookie.Cookies);
|
||||
if (downRes)
|
||||
{
|
||||
// 复制封面图片为fanart.jpg
|
||||
var fanartImgName = GetNfoFileName(cookie, item, config, "fanart.jpg");
|
||||
var copyPath = Path.Combine(saveFolder, fanartImgName);
|
||||
File.Copy(coverSavePath, copyPath, true);
|
||||
}
|
||||
//if (downRes)
|
||||
//{
|
||||
// // 复制封面图片为fanart.jpg
|
||||
// var fanartImgName = GetNfoFileName(cookie, item, config, "fanart.jpg");
|
||||
// var copyPath = Path.Combine(saveFolder, fanartImgName);
|
||||
// File.Copy(coverSavePath, copyPath, true);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -782,7 +783,7 @@ namespace dy.net.job
|
||||
|
||||
return new DouyinVideo
|
||||
{
|
||||
ViedoType = diffs.VideoType.GetHashCode().ToString(),
|
||||
ViedoType = diffs.VideoType,
|
||||
AwemeId = item.AwemeId,
|
||||
Author = item.Author?.Nickname,
|
||||
AuthorId = item.Author?.Uid,
|
||||
|
||||
+4
-1
@@ -36,7 +36,10 @@ namespace dy.net.model
|
||||
/// <summary>
|
||||
/// 日志保留天数,防止容器日志太多,默认10天
|
||||
/// </summary>
|
||||
public int KeepLogDay { get; set; } = 10;
|
||||
public int LogKeepDay { get; set; } = 10;
|
||||
|
||||
[SugarColumn(IsIgnore=true)]
|
||||
public bool DownImageVideoFromEnv { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using SqlSugar;
|
||||
using dy.net.dto;
|
||||
using dy.net.extension;
|
||||
using SqlSugar;
|
||||
|
||||
namespace dy.net.model
|
||||
{
|
||||
@@ -132,13 +134,13 @@ namespace dy.net.model
|
||||
public string SyncTimeStr => SyncTime.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
/// <summary>
|
||||
/// 1喜欢的,2收藏的,3关注的
|
||||
/// 1喜欢的,2收藏的,3关注的 ,4 图片视频
|
||||
/// </summary>
|
||||
[SugarColumn(Length =200,IsNullable =true)]
|
||||
public string ViedoType { get; set; }
|
||||
public VideoTypeEnum ViedoType { get; set; }
|
||||
|
||||
[SugarColumn(IsIgnore = true)]
|
||||
public string ViedoTypeStr => ViedoType == "1" ? "喜欢的" : (ViedoType == "2"? "收藏的":"关注的");
|
||||
public string ViedoTypeStr => ViedoType.GetDescription();
|
||||
|
||||
[SugarColumn(IsIgnore = true)]
|
||||
public string ViedoCate =>(string.IsNullOrWhiteSpace(Tag1) ? "" : Tag1) + "/" + (string.IsNullOrWhiteSpace(Tag2) ? "" : Tag2);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using dy.net.dto;
|
||||
using dy.net.extension;
|
||||
using dy.net.model;
|
||||
using Quartz.Util;
|
||||
using SqlSugar;
|
||||
@@ -37,13 +38,17 @@ namespace dy.net.repository
|
||||
{
|
||||
start = Convert.ToDateTime(dates[0]);
|
||||
}
|
||||
|
||||
VideoTypeEnum? enumviedoType = null;
|
||||
if (!string.IsNullOrEmpty(viedoType)&&viedoType!="*")
|
||||
{
|
||||
enumviedoType = viedoType.ToVideoTypeEnum();
|
||||
}
|
||||
var where = this.Db.Queryable<DouyinVideo>()
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(tag), x => x.Tag1 == tag)
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(author), x => x.Author == author)
|
||||
.WhereIF(start.HasValue, x => x.SyncTime >= start.Value)
|
||||
.WhereIF(end.HasValue, x => x.SyncTime <= end.Value)
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(viedoType) && viedoType != "*", x => x.ViedoType == viedoType);
|
||||
.WhereIF(enumviedoType.HasValue, x => x.ViedoType == enumviedoType);
|
||||
|
||||
|
||||
var totalCount = await where.CountAsync();
|
||||
@@ -72,7 +77,7 @@ namespace dy.net.repository
|
||||
public async Task<(string, string)> GetUperLastViedoFileName(string AuthorId,string ViedoNameSimplify)
|
||||
{
|
||||
|
||||
var video= await this.Db.Queryable<DouyinVideo>().Where(x => x.AuthorId == AuthorId && x.ViedoType == "3")
|
||||
var video= await this.Db.Queryable<DouyinVideo>().Where(x => x.AuthorId == AuthorId && x.ViedoType == VideoTypeEnum.UperPost)
|
||||
.Where(x => x.VideoTitleSimplify == ViedoNameSimplify)
|
||||
.OrderByDescending(x => x.CreateTime).FirstAsync();
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using ClockSnowFlake;
|
||||
using dy.net.dto;
|
||||
using dy.net.model;
|
||||
using dy.net.utils;
|
||||
using SqlSugar;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
@@ -18,7 +19,15 @@ namespace dy.net.service
|
||||
|
||||
public AppConfig GetConfig()
|
||||
{
|
||||
return sqlSugarClient.Queryable<AppConfig>().First();
|
||||
var config= sqlSugarClient.Queryable<AppConfig>().First();
|
||||
|
||||
var downImgConfig = Appsettings.Get(SystemStaticUtil.DOWN_IMAGE_VIDEO_ENABLE);
|
||||
if (!string.IsNullOrEmpty(downImgConfig))
|
||||
{
|
||||
downImgConfig = downImgConfig.ToLower();
|
||||
config.DownImageVideoFromEnv = downImgConfig == "1";
|
||||
}
|
||||
return config;
|
||||
}
|
||||
/// <summary>
|
||||
/// 初始化并返回配置
|
||||
@@ -42,7 +51,7 @@ namespace dy.net.service
|
||||
Cron = "30",
|
||||
BatchCount = 10,
|
||||
DownImageVideo = downLoadImage,
|
||||
KeepLogDay = 10
|
||||
LogKeepDay = 10
|
||||
};
|
||||
sqlSugarClient.Insertable(config).ExecuteCommand();
|
||||
return config;
|
||||
@@ -76,16 +85,28 @@ namespace dy.net.service
|
||||
/// </summary>
|
||||
public void UpdateCollectViedoType()
|
||||
{
|
||||
var collectViedos = sqlSugarClient.Queryable<DouyinVideo>().Where(x => string.IsNullOrWhiteSpace(x.ViedoType)).ToList();
|
||||
|
||||
if (collectViedos.Any())
|
||||
{
|
||||
collectViedos.ForEach(x =>
|
||||
{
|
||||
x.ViedoType = "2";
|
||||
});
|
||||
sqlSugarClient.Updateable(collectViedos).ExecuteCommand();
|
||||
}
|
||||
string sql= @"UPDATE dy_collect_video
|
||||
SET ViedoType = CASE
|
||||
WHEN ViedoType = '0' THEN 0
|
||||
WHEN ViedoType = '1' THEN 1
|
||||
WHEN ViedoType = '2' THEN 2
|
||||
WHEN ViedoType = '3' THEN 3
|
||||
WHEN ViedoType = '4' THEN 4
|
||||
ELSE NULL
|
||||
END;";
|
||||
|
||||
sqlSugarClient.Ado.ExecuteCommand(sql);
|
||||
//var collectViedos = sqlSugarClient.Queryable<DouyinVideo>().ToList();
|
||||
|
||||
//if (collectViedos.Any())
|
||||
//{
|
||||
// collectViedos.ForEach(x =>
|
||||
// {
|
||||
// x.ViedoType = x.;
|
||||
// });
|
||||
// sqlSugarClient.Updateable(collectViedos).ExecuteCommand();
|
||||
//}
|
||||
}
|
||||
/// <summary>
|
||||
/// 重置所有Cookie的同步状态为0
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace dy.net.service
|
||||
/// <returns></returns>
|
||||
public async Task StartJob(string expression,int delay=5000)
|
||||
{
|
||||
//await StartCollectJob(expression);
|
||||
await StartCollectJob(expression);
|
||||
|
||||
await Task.Delay(delay);
|
||||
//如果是数字则加1分钟,减少并发
|
||||
@@ -37,7 +37,7 @@ namespace dy.net.service
|
||||
cron2++;
|
||||
expression = cron2.ToString();
|
||||
}
|
||||
//await StartUperPostJob(expression);
|
||||
await StartUperPostJob(expression);
|
||||
}
|
||||
|
||||
private async Task<bool> StartCollectJob(string expression)
|
||||
|
||||
@@ -67,17 +67,26 @@ namespace dy.net.service
|
||||
CategoryCount = list.Select(x => x.Tag1).Distinct().Count(),
|
||||
VideoCount = list.Count,
|
||||
Categories = Categories,
|
||||
FavoriteCount = list.Count(x => x.ViedoType == "1"),
|
||||
CollectCount = list.Count(x => x.ViedoType == "2"),
|
||||
FollowCount = list.Count(x => x.ViedoType == "3"),
|
||||
FavoriteCount = list.Count(x => x.ViedoType == VideoTypeEnum.Favorite),
|
||||
CollectCount = list.Count(x => x.ViedoType == VideoTypeEnum.Collect),
|
||||
FollowCount = list.Count(x => x.ViedoType == VideoTypeEnum.UperPost),
|
||||
GraphicVideoCount = list.Count(x => x.ViedoType == VideoTypeEnum.ImageVideo),
|
||||
|
||||
VideoSizeTotal = ByteToGbConverter.ConvertBytesToGb(list.Sum(x => x.FileSize)),
|
||||
VideoFavoriteSize = ByteToGbConverter.ConvertBytesToGb(list.Where(x => x.ViedoType == "1").Sum(x => x.FileSize)),
|
||||
VideoCollectSize = ByteToGbConverter.ConvertBytesToGb(list.Where(x => x.ViedoType == "2").Sum(x => x.FileSize)),
|
||||
VideoFollowSize = ByteToGbConverter.ConvertBytesToGb(list.Where(x => x.ViedoType == "3").Sum(x => x.FileSize)),
|
||||
VideoFavoriteSize = ByteToGbConverter.ConvertBytesToGb(list.Where(x => x.ViedoType == VideoTypeEnum.Favorite).Sum(x => x.FileSize)),
|
||||
VideoCollectSize = ByteToGbConverter.ConvertBytesToGb(list.Where(x => x.ViedoType == VideoTypeEnum.Collect).Sum(x => x.FileSize)),
|
||||
VideoFollowSize = ByteToGbConverter.ConvertBytesToGb(list.Where(x => x.ViedoType == VideoTypeEnum.UperPost).Sum(x => x.FileSize)),
|
||||
GraphicVideoSize = ByteToGbConverter.ConvertBytesToGb(list.Where(x => x.ViedoType == VideoTypeEnum.ImageVideo).Sum(x => x.FileSize)),
|
||||
|
||||
//TotalDiskSize= ByteToGbConverter.GetHostTotalDiskSpaceGB(),
|
||||
};
|
||||
if (data.GraphicVideoSize == "0.00")
|
||||
{
|
||||
if(list.Where(x => x.ViedoType == VideoTypeEnum.ImageVideo).Sum(x => x.FileSize) > 0)
|
||||
{
|
||||
data.GraphicVideoSize = "<0.01";//避免显示0.00误导用户
|
||||
}
|
||||
}
|
||||
data.Authors = list.GroupBy(x => x.Author).Select(x => new VideoStaticsItemDto { Name = x.Key, Count = x.LongCount(), Icon = x.FirstOrDefault().AuthorAvatarUrl }).OrderByDescending(d => d.Count).ToList();
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
namespace dy.net.utils
|
||||
{
|
||||
public class LogFileCleaner
|
||||
{
|
||||
/// <summary>
|
||||
/// 清除指定目录下n天之前的日志文件
|
||||
/// </summary>
|
||||
/// <param name="logDirectory">日志文件所在目录</param>
|
||||
/// <param name="n">清除N天之前的日志文件</param>
|
||||
public static void CleanOldLogFiles(string logDirectory,int n)
|
||||
{
|
||||
// 计算n天前的时间点
|
||||
DateTime threeDaysAgo = DateTime.Now.AddDays(-n);
|
||||
|
||||
// 遍历日志目录下的所有文件
|
||||
foreach (string file in Directory.GetFiles(logDirectory))
|
||||
{
|
||||
FileInfo fileInfo = new FileInfo(file);
|
||||
// 判断文件修改时间是否早于3天前
|
||||
if (fileInfo.LastWriteTime < threeDaysAgo)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 删除符合条件的文件
|
||||
File.Delete(file);
|
||||
Console.WriteLine($"已删除文件: {file}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"删除文件 {file} 时发生异常: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace dy.net.utils
|
||||
{
|
||||
public class SystemStaticUtil
|
||||
{
|
||||
public static string DOWN_IMAGE_VIDEO_ENABLE="DOWN_IMGVIDEO";
|
||||
|
||||
public static string ASPNETCORE_URLS = "ASPNETCORE_URLS";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user