Compare commits
No commits in common. "main" and "master" have entirely different histories.
@ -1,237 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const Component = () => {
|
||||
return (
|
||||
<div className={styles.lunacyBackground}>
|
||||
<div className={styles.autoWrapper}>
|
||||
<img src="../image/mhcw5e6e-krjj7v6.svg" className={styles.frame} />
|
||||
<p className={styles.text}>AI棒榜</p>
|
||||
<img src="../image/mhcw5e6d-58eu18x.svg" className={styles.frame2} />
|
||||
</div>
|
||||
<div className={styles.frame3}>
|
||||
<p className={styles.text2}>微短剧爆火</p>
|
||||
<p className={styles.text3}>中国“血统”的ReelShort征服美国</p>
|
||||
</div>
|
||||
<img src="../image/mhcw5e6d-8fwj6ae.svg" className={styles.frame4} />
|
||||
<div className={styles.autoWrapper2}>
|
||||
<p className={styles.a2}>日榜 2025年10月19日/周日</p>
|
||||
<img src="../image/mhcw5e6e-zupmsq9.svg" className={styles.frame5} />
|
||||
</div>
|
||||
<div className={styles.autoWrapper3}>
|
||||
<div className={styles.frame6}>
|
||||
<p className={styles.a22}>全部</p>
|
||||
</div>
|
||||
<div className={styles.frame7}>
|
||||
<p className={styles.a23}>小说</p>
|
||||
</div>
|
||||
<div className={styles.frame7}>
|
||||
<p className={styles.a23}>动漫</p>
|
||||
</div>
|
||||
<div className={styles.frame7}>
|
||||
<p className={styles.a23}>短剧</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.frame39}>
|
||||
<div className={styles.frame17}>
|
||||
<div className={styles.frame16}>
|
||||
<img src="../image/mhcw5e6e-u278byr.png" className={styles.frame8} />
|
||||
<div className={styles.autoWrapper7}>
|
||||
<p className={styles.a24}>奶团</p>
|
||||
<div className={styles.autoWrapper5}>
|
||||
<div className={styles.autoWrapper4}>
|
||||
<img
|
||||
src="../image/mhcw5e6e-5gc00qy.svg"
|
||||
className={styles.frame9}
|
||||
/>
|
||||
<img
|
||||
src="../image/mhcw5e6e-19alacf.svg"
|
||||
className={styles.frame10}
|
||||
/>
|
||||
<img
|
||||
src="../image/mhcw5e6e-7kk4jjy.svg"
|
||||
className={styles.frame11}
|
||||
/>
|
||||
</div>
|
||||
<p className={styles.a25}>
|
||||
剧场名:爱微剧场
|
||||
<br />
|
||||
承制 :妙想制片厂
|
||||
<br />
|
||||
版权:可梦
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.autoWrapper6}>
|
||||
<img
|
||||
src="../image/mhcw5e6e-6bvf0dd.svg"
|
||||
className={styles.frame12}
|
||||
/>
|
||||
<p className={styles.a26}>9999W</p>
|
||||
<img
|
||||
src="../image/mhcw5e6e-5hzy6ee.svg"
|
||||
className={styles.frame13}
|
||||
/>
|
||||
<p className={styles.a26}>374W</p>
|
||||
</div>
|
||||
</div>
|
||||
<img src="../image/mhcw5e6e-5x5ujui.svg" className={styles.frame14} />
|
||||
<p className={styles.a27}>300W</p>
|
||||
<div className={styles.frame15}>
|
||||
<p className={styles.a1}>1</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className={styles.a28}>用户评论总结</p>
|
||||
</div>
|
||||
<img src="../image/mhcw5e6e-epiv9as.svg" className={styles.frame18} />
|
||||
<div className={styles.frame23}>
|
||||
<img src="../image/mhcw5e6e-kdsx4vj.png" className={styles.frame8} />
|
||||
<div className={styles.autoWrapper9}>
|
||||
<p className={styles.a24}>奶团</p>
|
||||
<div className={styles.frame20}>
|
||||
<img src="../image/mhcw5e6e-5gc00qy.svg" className={styles.frame19} />
|
||||
<p className={styles.a25}>剧场名:爱微剧场 </p>
|
||||
</div>
|
||||
<div className={styles.autoWrapper8}>
|
||||
<img src="../image/mhcw5e6e-6kmpbld.svg" className={styles.frame12} />
|
||||
<p className={styles.a26}>9999W</p>
|
||||
<img src="../image/mhcw5e6e-l10msdd.svg" className={styles.frame13} />
|
||||
<p className={styles.a26}>374W</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.autoWrapper11}>
|
||||
<div className={styles.autoWrapper10}>
|
||||
<img src="../image/mhcw5e6e-5x5ujui.svg" className={styles.frame21} />
|
||||
<p className={styles.a29}>300W</p>
|
||||
</div>
|
||||
<p className={styles.a210}>用户评论总结</p>
|
||||
</div>
|
||||
<div className={styles.frame22}>
|
||||
<p className={styles.a1}>2</p>
|
||||
</div>
|
||||
</div>
|
||||
<img src="../image/mhcw5e6e-epiv9as.svg" className={styles.frame24} />
|
||||
<div className={styles.frame28}>
|
||||
<div className={styles.frame27}>
|
||||
<img src="../image/mhcw5e6e-myt1pbz.png" className={styles.frame8} />
|
||||
<div className={styles.autoWrapper15}>
|
||||
<p className={styles.a24}>奶团</p>
|
||||
<div className={styles.autoWrapper13}>
|
||||
<div className={styles.autoWrapper12}>
|
||||
<img
|
||||
src="../image/mhcw5e6e-5gc00qy.svg"
|
||||
className={styles.frame9}
|
||||
/>
|
||||
<img
|
||||
src="../image/mhcw5e6e-19alacf.svg"
|
||||
className={styles.frame10}
|
||||
/>
|
||||
</div>
|
||||
<p className={styles.a25}>
|
||||
剧场名:爱微剧场
|
||||
<br />
|
||||
承制 :妙想制片厂
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.autoWrapper14}>
|
||||
<img
|
||||
src="../image/mhcw5e6e-6kmpbld.svg"
|
||||
className={styles.frame12}
|
||||
/>
|
||||
<p className={styles.a26}>9999W</p>
|
||||
<img
|
||||
src="../image/mhcw5e6e-l10msdd.svg"
|
||||
className={styles.frame13}
|
||||
/>
|
||||
<p className={styles.a26}>374W</p>
|
||||
</div>
|
||||
</div>
|
||||
<img src="../image/mhcw5e6e-5x5ujui.svg" className={styles.frame25} />
|
||||
<p className={styles.a27}>300W</p>
|
||||
<div className={styles.frame26}>
|
||||
<p className={styles.a1}>3</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className={styles.a211}>用户评论总结</p>
|
||||
</div>
|
||||
<img src="../image/mhcw5e6e-epiv9as.svg" className={styles.frame24} />
|
||||
<div className={styles.frame33}>
|
||||
<div className={styles.frame30}>
|
||||
<div className={styles.frame29}>
|
||||
<p className={styles.a1}>4</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.autoWrapper17}>
|
||||
<p className={styles.a24}>奶团</p>
|
||||
<div className={styles.frame32}>
|
||||
<img src="../image/mhcw5e6e-cpzpf35.svg" className={styles.frame31} />
|
||||
<p className={styles.a25}>
|
||||
剧场名:爱微剧场
|
||||
<br />
|
||||
承制 :妙想制片厂
|
||||
<br />
|
||||
版权:可梦
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.autoWrapper16}>
|
||||
<img src="../image/mhcw5e6e-6kmpbld.svg" className={styles.frame12} />
|
||||
<p className={styles.a26}>9999W</p>
|
||||
<img src="../image/mhcw5e6e-l10msdd.svg" className={styles.frame13} />
|
||||
<p className={styles.a26}>374W</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.autoWrapper19}>
|
||||
<div className={styles.autoWrapper18}>
|
||||
<img src="../image/mhcw5e6e-5x5ujui.svg" className={styles.frame21} />
|
||||
<p className={styles.a29}>300W</p>
|
||||
</div>
|
||||
<p className={styles.a210}>用户评论总结</p>
|
||||
</div>
|
||||
</div>
|
||||
<img src="../image/mhcw5e6e-epiv9as.svg" className={styles.frame24} />
|
||||
<div className={styles.frame38}>
|
||||
<div className={styles.frame37}>
|
||||
<div className={styles.frame35}>
|
||||
<div className={styles.frame34}>
|
||||
<p className={styles.a1}>5</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.autoWrapper17}>
|
||||
<p className={styles.a24}>奶团</p>
|
||||
<div className={styles.frame32}>
|
||||
<img
|
||||
src="../image/mhcw5e6e-cpzpf35.svg"
|
||||
className={styles.frame31}
|
||||
/>
|
||||
<p className={styles.a25}>
|
||||
剧场名:爱微剧场
|
||||
<br />
|
||||
承制 :妙想制片厂
|
||||
<br />
|
||||
版权:可梦
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.autoWrapper16}>
|
||||
<img
|
||||
src="../image/mhcw5e6e-6kmpbld.svg"
|
||||
className={styles.frame12}
|
||||
/>
|
||||
<p className={styles.a26}>9999W</p>
|
||||
<img
|
||||
src="../image/mhcw5e6e-l10msdd.svg"
|
||||
className={styles.frame13}
|
||||
/>
|
||||
<p className={styles.a26}>374W</p>
|
||||
</div>
|
||||
</div>
|
||||
<img src="../image/mhcw5e6e-5x5ujui.svg" className={styles.frame36} />
|
||||
<p className={styles.a27}>300W</p>
|
||||
</div>
|
||||
<p className={styles.a212}>用户评论总结</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Component;
|
||||
@ -1,659 +0,0 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.lunacyBackground {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
border-radius: 38px;
|
||||
background: #ebedf2;
|
||||
padding: 46px 13px 36px 30px;
|
||||
width: 856px;
|
||||
height: 2013px;
|
||||
|
||||
.autoWrapper {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
padding-right: 0;
|
||||
padding-left: 304px;
|
||||
|
||||
.frame {
|
||||
margin-top: 9px;
|
||||
width: 50px;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin: 0px 0px 0px 12px;
|
||||
line-height: 51px;
|
||||
letter-spacing: 1.9px;
|
||||
color: #272727;
|
||||
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.frame2 {
|
||||
margin-top: 10px;
|
||||
margin-left: 68px;
|
||||
width: 259px;
|
||||
height: 69px;
|
||||
}
|
||||
}
|
||||
|
||||
.frame3 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
margin-top: 13px;
|
||||
margin-right: 17px;
|
||||
padding: 53px 58px 64px 35px;
|
||||
background-image: url(../image/mhcw5e6d-c7qpzc4.png);
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
.text2 {
|
||||
margin: 0px 0px 0px 156px;
|
||||
width: 414px;
|
||||
text-align: center;
|
||||
line-height: 60px;
|
||||
letter-spacing: 0;
|
||||
color: #ffffff;
|
||||
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
|
||||
font-size: 37px;
|
||||
text-shadow: 0px 5px 11px #000000;
|
||||
}
|
||||
|
||||
.text3 {
|
||||
width: 703px;
|
||||
text-align: center;
|
||||
line-height: 50px;
|
||||
letter-spacing: 0;
|
||||
color: #ffffff;
|
||||
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
|
||||
font-size: 48px;
|
||||
text-shadow: 0px 0px 11px #000000;
|
||||
}
|
||||
}
|
||||
|
||||
.frame4 {
|
||||
margin-top: 30px;
|
||||
margin-left: 355px;
|
||||
width: 85px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.autoWrapper2 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
justify-content: space-between;
|
||||
margin-top: 50px;
|
||||
padding-right: 226px;
|
||||
padding-left: 220px;
|
||||
|
||||
.a2 {
|
||||
line-height: 36px;
|
||||
letter-spacing: 0;
|
||||
color: #222222;
|
||||
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.frame5 {
|
||||
margin-top: 13px;
|
||||
width: 21px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.autoWrapper3 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
justify-content: space-between;
|
||||
margin-top: 47px;
|
||||
padding-right: 35px;
|
||||
padding-left: 0;
|
||||
|
||||
.frame6 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
border-radius: 28px;
|
||||
background: #41a4fd;
|
||||
padding: 16px 62px 18px;
|
||||
|
||||
.a22 {
|
||||
line-height: 36px;
|
||||
letter-spacing: 0;
|
||||
color: #ffffff;
|
||||
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.frame7 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
border-radius: 28px;
|
||||
background: #d7dbdf;
|
||||
padding: 16px 62px 18px;
|
||||
|
||||
.a23 {
|
||||
line-height: 36px;
|
||||
letter-spacing: 0;
|
||||
color: #6d6f74;
|
||||
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.frame39 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
margin-top: 30px;
|
||||
margin-right: 17px;
|
||||
border-radius: 35px;
|
||||
background: #ffffff;
|
||||
padding: 29px 29px 36px 28px;
|
||||
|
||||
.a210 {
|
||||
margin: 130px 0px 0px;
|
||||
line-height: 26px;
|
||||
letter-spacing: 0;
|
||||
color: #6d6f74;
|
||||
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.a29 {
|
||||
line-height: 41px;
|
||||
letter-spacing: 0;
|
||||
color: #ff3419;
|
||||
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.frame21 {
|
||||
margin-top: 6px;
|
||||
width: 26px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.a1 {
|
||||
line-height: 25px;
|
||||
letter-spacing: 0;
|
||||
color: #ffffff;
|
||||
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.a27 {
|
||||
margin: 0px 0px 0px 7px;
|
||||
line-height: 41px;
|
||||
letter-spacing: 0;
|
||||
color: #ff3419;
|
||||
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.frame10 {
|
||||
margin-top: 18px;
|
||||
width: 20px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.frame9 {
|
||||
width: 22px;
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
.frame8 {
|
||||
width: 150px;
|
||||
height: 204px;
|
||||
}
|
||||
|
||||
.frame13 {
|
||||
margin-top: 7px;
|
||||
margin-left: 45px;
|
||||
width: 26px;
|
||||
height: 23px;
|
||||
}
|
||||
|
||||
.a26 {
|
||||
margin: 0px 0px 0px 10px;
|
||||
line-height: 33px;
|
||||
letter-spacing: 0;
|
||||
color: #6d6f74;
|
||||
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.frame12 {
|
||||
margin-top: 5px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
.a25 {
|
||||
line-height: 33px;
|
||||
letter-spacing: 0;
|
||||
color: #a5a8b1;
|
||||
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.a24 {
|
||||
line-height: 41px;
|
||||
letter-spacing: 0;
|
||||
color: #222222;
|
||||
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.autoWrapper17 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
margin-left: 25px;
|
||||
|
||||
.frame32 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
justify-content: space-between;
|
||||
margin-top: 7px;
|
||||
min-width: 267px;
|
||||
|
||||
.frame31 {
|
||||
margin-top: 11px;
|
||||
width: 22px;
|
||||
height: 94px;
|
||||
}
|
||||
}
|
||||
|
||||
.autoWrapper16 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
margin-top: 14px;
|
||||
padding-right: 1px;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.frame17 {
|
||||
position: relative;
|
||||
width: 738px;
|
||||
height: 205px;
|
||||
|
||||
.frame16 {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
align-items: flex-start;
|
||||
padding-top: 1px;
|
||||
padding-left: 1px;
|
||||
width: 737px;
|
||||
height: 205px;
|
||||
|
||||
.autoWrapper7 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
margin-left: 25px;
|
||||
|
||||
.autoWrapper5 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
justify-content: space-between;
|
||||
margin-top: 7px;
|
||||
padding-right: 0;
|
||||
padding-left: 2px;
|
||||
|
||||
.autoWrapper4 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
margin-top: 8px;
|
||||
|
||||
.frame11 {
|
||||
margin-top: 16px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.autoWrapper6 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
margin-top: 16px;
|
||||
padding-right: 3px;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.frame14 {
|
||||
margin-top: 6px;
|
||||
margin-left: 177px;
|
||||
width: 26px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.frame15 {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
align-items: center;
|
||||
border-radius: 9px;
|
||||
padding: 12px 19px 12px 10px;
|
||||
width: 39px;
|
||||
height: 49px;
|
||||
background-image: linear-gradient(180deg, #ffe100 0%, #ffb200 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.a28 {
|
||||
position: absolute;
|
||||
top: 173px;
|
||||
left: 570px;
|
||||
width: 168px;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
letter-spacing: 0;
|
||||
color: #6d6f74;
|
||||
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.frame18 {
|
||||
margin-top: 31px;
|
||||
width: 738px;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
.frame23 {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
margin: 30px 1px 0px;
|
||||
padding-top: 1px;
|
||||
padding-left: 1px;
|
||||
|
||||
.autoWrapper9 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
margin-left: 25px;
|
||||
|
||||
.frame20 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
justify-content: space-between;
|
||||
margin-top: 7px;
|
||||
margin-right: 24px;
|
||||
min-width: 242px;
|
||||
|
||||
.frame19 {
|
||||
margin-top: 8px;
|
||||
width: 22px;
|
||||
height: 21px;
|
||||
}
|
||||
}
|
||||
|
||||
.autoWrapper8 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
margin-top: 86px;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.autoWrapper11 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
margin-left: 125px;
|
||||
|
||||
.autoWrapper10 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
justify-content: space-between;
|
||||
padding-right: 0;
|
||||
padding-left: 55px;
|
||||
}
|
||||
}
|
||||
|
||||
.frame22 {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
align-items: center;
|
||||
border-radius: 9px;
|
||||
padding: 12px 12px 12px 10px;
|
||||
width: 39px;
|
||||
height: 49px;
|
||||
background-image: linear-gradient(180deg, #c9d8dc 0%, #9ca6b2 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.frame24 {
|
||||
margin-top: 30px;
|
||||
width: 738px;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
.frame28 {
|
||||
position: relative;
|
||||
margin-top: 30px;
|
||||
width: 738px;
|
||||
height: 205px;
|
||||
|
||||
.frame27 {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
align-items: flex-start;
|
||||
padding-top: 1px;
|
||||
padding-left: 1px;
|
||||
width: 737px;
|
||||
height: 205px;
|
||||
|
||||
.autoWrapper15 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
margin-left: 25px;
|
||||
|
||||
.autoWrapper13 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
justify-content: space-between;
|
||||
margin-top: 7px;
|
||||
padding-right: 9px;
|
||||
padding-left: 0;
|
||||
|
||||
.autoWrapper12 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.autoWrapper14 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
margin-top: 53px;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.frame25 {
|
||||
margin-top: 6px;
|
||||
margin-left: 180px;
|
||||
width: 26px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.frame26 {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
align-items: center;
|
||||
border-radius: 9px;
|
||||
padding: 12px 12px 12px 10px;
|
||||
width: 39px;
|
||||
height: 49px;
|
||||
background-image: linear-gradient(180deg, #de8260 0%, #ae3829 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.a211 {
|
||||
position: absolute;
|
||||
top: 172px;
|
||||
left: 570px;
|
||||
width: 168px;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
letter-spacing: 0;
|
||||
color: #6d6f74;
|
||||
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.frame33 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
margin-top: 30px;
|
||||
margin-right: 3px;
|
||||
|
||||
.frame30 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding-right: 111px;
|
||||
padding-bottom: 155px;
|
||||
background-image: url(../image/mhcw5e6e-824j2v2.png);
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
.frame29 {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
border-radius: 9px;
|
||||
background: #000000a6;
|
||||
padding: 12px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.autoWrapper19 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
margin-left: 126px;
|
||||
|
||||
.autoWrapper18 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
justify-content: space-between;
|
||||
padding-right: 0;
|
||||
padding-left: 53px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.frame38 {
|
||||
position: relative;
|
||||
margin-top: 30px;
|
||||
margin-left: 2px;
|
||||
width: 737px;
|
||||
height: 204px;
|
||||
|
||||
.frame37 {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
align-items: flex-start;
|
||||
width: 736px;
|
||||
height: 204px;
|
||||
|
||||
.frame35 {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding-right: 111px;
|
||||
padding-bottom: 155px;
|
||||
background-image: url(../image/mhcw5e6e-7s1hyk3.png);
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
.frame34 {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
border-radius: 9px;
|
||||
background: #000000a6;
|
||||
padding: 12px 12px 12px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.frame36 {
|
||||
margin-top: 6px;
|
||||
margin-left: 179px;
|
||||
width: 26px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.a212 {
|
||||
position: absolute;
|
||||
top: 171px;
|
||||
left: 569px;
|
||||
width: 168px;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
letter-spacing: 0;
|
||||
color: #6d6f74;
|
||||
font-family: Alatsi, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 850 KiB |
|
Before Width: | Height: | Size: 850 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
@ -1,24 +0,0 @@
|
||||
<svg width="259" height="69" viewBox="0 0 259 69" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_0_7)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 34.5C0 15.4462 15.4462 0 34.5 0H224.5C243.554 0 259 15.4462 259 34.5C259 53.5538 243.554 69 224.5 69H34.5C15.4462 69 0 53.5538 0 34.5Z" fill="white" fill-opacity="0.74902"/>
|
||||
</g>
|
||||
<circle cx="51.5" cy="34.5" r="7.5" fill="black"/>
|
||||
<circle cx="209.5" cy="34.5" r="18.5" fill="black"/>
|
||||
<circle cx="209.5" cy="34.5" r="5.5" fill="black"/>
|
||||
<circle cx="35.5" cy="34.5" r="4.5" fill="black"/>
|
||||
<circle cx="66.5" cy="34.5" r="4.5" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M115 34.5C115 32.567 116.567 31 118.5 31H141.5C143.433 31 145 32.567 145 34.5C145 36.433 143.433 38 141.5 38H118.5C116.567 38 115 36.433 115 34.5Z" fill="black"/>
|
||||
<rect x="87" y="15" width="4" height="39" fill="#959595" fill-opacity="0.74902"/>
|
||||
<rect x="169" y="15" width="4" height="39" fill="#959595" fill-opacity="0.74902"/>
|
||||
<defs>
|
||||
<filter id="filter0_d_0_7" x="-4" y="-4" width="267" height="77" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.392157 0 0 0 0 0.392157 0 0 0 0 0.392157 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_0_7"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_0_7" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
@ -1,7 +0,0 @@
|
||||
<svg width="85" height="12" viewBox="0 0 85 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="4.5" cy="6.5" r="4.5" fill="#C0C4C8"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 6C15 2.68629 17.6863 0 21 0H34C37.3137 0 40 2.68629 40 6C40 9.31371 37.3137 12 34 12H21C17.6863 12 15 9.31371 15 6Z" fill="#41A4FD"/>
|
||||
<circle cx="50.5" cy="6.5" r="4.5" fill="#C0C4C8"/>
|
||||
<circle cx="65.5" cy="6.5" r="4.5" fill="#C0C4C8"/>
|
||||
<circle cx="80.5" cy="6.5" r="4.5" fill="#C0C4C8"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 497 B |
@ -1,3 +0,0 @@
|
||||
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.2008 12.9985L10.4014 18.1985V7.79852L18.2008 12.9985L18.2008 12.9985ZM12.9998 23.3985C18.7447 23.3985 23.401 18.7427 23.401 12.9985C23.401 7.25429 18.7447 2.59851 12.9998 2.59851C7.25495 2.59851 2.59863 7.25728 2.59863 12.9985C2.59863 18.7397 7.25495 23.3985 12.9998 23.3985V23.3985ZM13 26C5.81965 26 0 20.181 0 13.0015C0 5.82197 5.81965 0 13 0C20.1804 0 26 5.81898 26 12.9985C26 20.178 20.1804 26 13 26V26Z" fill="#6D6F74"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 582 B |
@ -1,3 +0,0 @@
|
||||
<svg width="20" height="18" viewBox="0 0 20 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.0559 0H0.945609C0.423363 0 0 0.447715 0 1V17C0 17.552 0.423633 18 0.945609 18H19.0559C19.5764 18 20 17.552 20 17V1C19.9992 0.448095 19.5763 0.000888824 19.0544 0H19.0559ZM4.50451 16H1.8916V13.9968H4.51359V11.9968H1.8916V9.9968H4.51359V7.9968H1.8916V5.9968H4.51359V3.9968H1.8916V2H4.50451V16V16ZM18.1093 3.99519L16.0637 3.99519V5.99519L18.1093 5.99519V7.99519L16.0637 7.99519V9.99519L18.1093 9.99519V11.9952H16.0637V13.9952L18.1093 13.9952V15.9968H16.041V1.9968H18.1093V3.9952V3.99519Z" fill="#AFB1B7"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 659 B |
@ -1,3 +0,0 @@
|
||||
<svg width="738" height="2" viewBox="0 0 738 2" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 1H738" stroke="#E1E3E5" stroke-linecap="square"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 165 B |
|
Before Width: | Height: | Size: 243 KiB |
|
Before Width: | Height: | Size: 190 KiB |
@ -1,3 +0,0 @@
|
||||
<svg width="21" height="12" viewBox="0 0 21 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.2205 0C19.6854 0 20.0621 0.0872106 20.3507 0.261632C20.6393 0.436053 20.8317 0.665972 20.9279 0.951388C21.024 1.2368 21.024 1.5579 20.9279 1.91467C20.8317 2.27144 20.6233 2.62424 20.3026 2.97309C19.0041 4.40017 17.7536 5.79553 16.5512 7.15919C15.3488 8.52284 14.1865 9.79929 13.0643 10.9885C12.7597 11.3057 12.403 11.5514 11.9942 11.7259C11.5854 11.9003 11.1646 11.9914 10.7317 11.9994C10.2989 12.0073 9.88205 11.9399 9.48126 11.7972C9.08047 11.6545 8.7438 11.4325 8.47127 11.1312C7.84603 10.4494 7.19675 9.75172 6.52342 9.03818L4.45534 6.89756C3.78201 6.18402 3.11269 5.48237 2.44738 4.79262C1.78206 4.10286 1.16084 3.43293 0.583698 2.78281C0.311159 2.48154 0.134811 2.16837 0.0546533 1.84332C-0.0255049 1.51826 -0.0174891 1.22095 0.0787007 0.95139C0.174891 0.681831 0.359255 0.45984 0.631792 0.285419C0.90433 0.110998 1.25703 0.0237872 1.68988 0.0237872C2.25099 0.0237872 3.02452 0.019823 4.01046 0.011895C4.99641 0.00396702 6.07454 3.18977e-06 7.24485 2.83536e-06H10.852H14.3869H17.3448H19.2205V0Z" fill="#41A4FD"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,5 +0,0 @@
|
||||
<svg width="23" height="94" viewBox="0 0 23 94" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 7.6475C0 8.16531 0 14.2805 0 14.2805C0 16.59 1.5961 18.2743 2.9357 19.2803C4.49022 20.4491 6.05075 21.0036 6.34432 21.0597C6.6374 21.0036 8.19793 20.4491 9.75294 19.2803C11.092 18.2743 12.6881 16.59 12.6881 14.2805V13.7094C12.0797 13.3309 11.5066 12.8983 10.9758 12.4169C9.98786 13.0481 8.71463 13.012 7.76407 12.3257C7.56166 12.1719 7.51867 11.8848 7.66715 11.6785C7.81563 11.4721 8.1015 11.4217 8.31164 11.5647C8.89103 11.9827 9.65433 12.0452 10.294 11.727C10.1828 11.6028 10.0761 11.4765 9.97387 11.3483C9.4759 10.7211 9.10268 10.0648 8.85671 9.38445L8.6007 8.44662L8.58267 8.34643L8.58067 8.33541C8.57466 8.29922 8.56899 8.26299 8.56363 8.22671L8.56163 8.21319L8.54661 8.10548L8.5451 8.08944C8.5401 8.05437 8.53608 8.0183 8.53258 7.98274L8.53157 7.97272C8.52026 7.86458 8.5119 7.75616 8.50653 7.64757C8.50653 7.63454 8.50552 7.62202 8.50502 7.60899L8.50302 7.53885L8.50101 7.48124C6.62838 7.62753 4.55635 7.57593 2.85755 7.33295C1.58909 7.1511 1.57376 7.10999 1.07139 6.96036C0.518555 6.79431 0 7.12969 0 7.6475ZM6.35453 15.4708H6.33449C5.40904 15.4719 4.60439 16.1058 4.38672 17.0053H8.3023C8.08447 16.1059 7.27991 15.4721 6.35453 15.4708H6.35453ZM21.0654 0.431909C19.7504 0.192045 18.9529 -0.000125216 15.781 2.63281e-08C12.6091 0.000125268 11.8126 0.236747 10.5555 0.450036C9.8584 0.572662 9.4375 1.21924 9.4375 2C9.4375 3.93591 9.4375 7.35551 9.4375 7.35551C9.4375 7.41963 9.439 7.48375 9.44151 7.54688L9.44301 7.58796C9.44602 7.64908 9.45002 7.7097 9.45503 7.76981L9.45854 7.80638C9.46305 7.85848 9.46856 7.91009 9.47507 7.96119L9.48008 8.00126C9.4871 8.05637 9.49561 8.11198 9.50513 8.16709L9.51515 8.2247C9.52763 8.29497 9.54166 8.36497 9.55723 8.43462L9.58328 8.54483L9.60332 8.62248C9.62771 8.71476 9.65478 8.80632 9.68448 8.89712C10.3478 10.9279 12.2014 12.3251 13.3987 13.0456L13.4072 13.0516C14.5645 13.748 15.5539 14.0911 15.7813 14.1347C16.0744 14.0786 17.6348 13.524 19.1905 12.3557C20.5291 11.3492 22.1251 9.66562 22.1251 7.35552C22.1251 7.35552 22.1248 2.85222 22.1249 2.00018C22.125 1.14813 21.6113 0.536591 21.0654 0.431909ZM1.47105 11.6714C1.62246 11.4615 1.91529 11.414 2.12531 11.5652C2.79752 12.0504 3.70497 12.0504 4.37717 11.5652C4.58732 11.4222 4.87319 11.4726 5.02166 11.679C5.17014 11.8853 5.12715 12.1724 4.92474 12.3262C3.9256 13.0473 2.57689 13.0473 1.57775 12.3262C1.3677 12.1749 1.31993 11.8821 1.47105 11.6719V11.6714ZM17.7388 9.01526H13.8232C14.0411 9.91465 14.8456 10.5485 15.771 10.5497H15.7911C16.7165 10.5487 17.5212 9.91475 17.7388 9.01526L17.7388 9.01526ZM17.2011 4.6227C18.2003 3.90213 19.5485 3.90213 20.5476 4.6227C20.691 4.7176 20.7709 4.88334 20.7559 5.05461C20.7409 5.22588 20.6334 5.37521 20.4757 5.44375C20.3181 5.51229 20.1355 5.48905 20.0001 5.38318C19.328 4.89819 18.4208 4.89819 17.7487 5.38318C17.613 5.4863 17.432 5.50766 17.276 5.43897C17.1201 5.37028 17.0136 5.22237 16.9981 5.05264C16.9825 4.88292 17.0602 4.71811 17.2011 4.6222L17.2011 4.6227ZM11.0141 4.6227C12.0133 3.90189 13.3618 3.90189 14.3611 4.6227C14.5044 4.7176 14.5844 4.88334 14.5694 5.05461C14.5544 5.22588 14.4468 5.37521 14.2892 5.44374C14.1315 5.51228 13.949 5.48904 13.8135 5.38318C13.1414 4.89819 12.2342 4.89819 11.5621 5.38318C11.4263 5.48256 11.2475 5.50148 11.0939 5.43274C10.9402 5.36401 10.8352 5.21817 10.8187 5.05063C10.8022 4.8831 10.8768 4.71958 11.0141 4.6222L11.0141 4.6227Z" fill="#AFB1B7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.1184 36H2.00811C1.48586 36 1.0625 36.4477 1.0625 37V53C1.0625 53.552 1.48613 54 2.00811 54H20.1184C20.6389 54 21.0625 53.552 21.0625 53V37C21.0617 36.4481 20.6388 36.0009 20.1169 36H20.1184ZM5.56701 52H2.9541V49.9968H5.57609V47.9968H2.9541V45.9968H5.57609V43.9968H2.9541V41.9968H5.57609V39.9968H2.9541V38H5.56701V52V52ZM19.1718 39.9952L17.1262 39.9952V41.9952L19.1718 41.9952V43.9952L17.1262 43.9952V45.9952L19.1718 45.9952V47.9952H17.1262V49.9952L19.1718 49.9952V51.9968H17.1035V37.9968H19.1718V39.9952V39.9952Z" fill="#AFB1B7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.0625 72C17.1345 72 22.0625 76.928 22.0625 83C22.0625 89.072 17.1345 94 11.0625 94C4.9905 94 0.0625 89.072 0.0625 83C0.0625 76.928 4.9905 72 11.0625 72V72ZM11.0625 77.5C8.0265 77.5 5.5625 79.964 5.5625 83C5.56591 85.4706 7.21552 87.636 9.59643 88.2952C11.9773 88.9545 14.5058 87.946 15.7794 85.8291L13.8929 84.6983C13.1293 85.97 11.6111 86.5759 10.1818 86.1795C8.75244 85.7831 7.76322 84.4817 7.7637 82.9985C7.76418 81.5152 8.75425 80.2145 10.1838 79.819C11.6134 79.4235 13.1312 80.0304 13.894 81.3026L15.7794 80.1696C14.7853 78.5129 12.9946 77.4994 11.0625 77.5H11.0625Z" fill="#AFB1B7"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 227 KiB |
@ -1,3 +0,0 @@
|
||||
<svg width="26" height="23" viewBox="0 0 26 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.8674 2.08791C22.524 0.748704 20.698 0 18.7465 0C16.7951 0 14.9691 0.748704 13.6048 2.10824L13.0099 2.69215L12.4121 2.10137C11.0409 0.746233 9.21431 0 7.26929 0C5.32428 0 3.49764 0.746232 2.12649 2.10137C0.757832 3.44305 0 5.2478 0 7.17641C0 9.10503 0.757832 10.9098 2.13343 12.258L4.41082 14.5091L11.3269 21.3447L13.0018 23L14.6787 21.3466L21.6106 14.511L23.8871 12.2457C25.2227 10.9477 26 9.09294 26 7.17641C25.9997 5.25988 25.2227 3.40515 23.8674 2.08791L23.8674 2.08791ZM22.2037 10.5942L19.935 12.8521L13.003 19.6874L6.08694 12.8518L3.80261 10.5939C1.89344 8.72267 1.89344 5.62981 3.80261 3.75832C4.75719 2.81489 6.01274 2.34331 7.26857 2.34331C8.5244 2.34331 9.77995 2.81489 10.7345 3.75832L12.2374 5.24392H13.7745L15.2879 3.75832C16.2347 2.81489 17.4903 2.34331 18.7461 2.34331C20.0019 2.34331 21.2575 2.81489 22.204 3.75832C23.1586 4.68637 23.628 5.93879 23.628 7.1761C23.628 8.41342 23.1583 9.66611 22.2037 10.5942V10.5942Z" fill="#6D6F74"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,3 +0,0 @@
|
||||
<svg width="23" height="22" viewBox="0 0 23 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 7.6475C0 8.16531 0 14.2805 0 14.2805C0 16.59 1.5961 18.2743 2.9357 19.2803C4.49022 20.4491 6.05075 21.0036 6.34432 21.0597C6.6374 21.0036 8.19793 20.4491 9.75294 19.2803C11.092 18.2743 12.6881 16.59 12.6881 14.2805V13.7094C12.0797 13.3309 11.5066 12.8983 10.9758 12.4169C9.98786 13.0481 8.71463 13.012 7.76407 12.3257C7.56166 12.1719 7.51867 11.8848 7.66715 11.6785C7.81563 11.4721 8.1015 11.4217 8.31164 11.5647C8.89103 11.9827 9.65433 12.0452 10.294 11.727C10.1828 11.6028 10.0761 11.4765 9.97387 11.3483C9.4759 10.7211 9.10268 10.0648 8.85671 9.38445L8.6007 8.44662L8.58267 8.34643L8.58067 8.33541C8.57466 8.29922 8.56899 8.26299 8.56363 8.22671L8.56163 8.21319L8.54661 8.10548L8.5451 8.08944C8.5401 8.05437 8.53608 8.0183 8.53258 7.98274L8.53157 7.97272C8.52026 7.86458 8.5119 7.75616 8.50653 7.64757C8.50653 7.63454 8.50552 7.62202 8.50502 7.60899L8.50302 7.53885L8.50101 7.48124C6.62838 7.62753 4.55635 7.57593 2.85755 7.33295C1.58909 7.1511 1.57376 7.10999 1.07139 6.96036C0.518555 6.79431 0 7.12969 0 7.6475ZM6.35453 15.4708H6.33449C5.40904 15.4719 4.60439 16.1058 4.38672 17.0053H8.3023C8.08447 16.1059 7.27991 15.4721 6.35453 15.4708H6.35453ZM21.0654 0.431909C19.7504 0.192045 18.9529 -0.000125216 15.781 2.63279e-08C12.6091 0.000125268 11.8126 0.236747 10.5555 0.450036C9.8584 0.572662 9.4375 1.21924 9.4375 2C9.4375 3.93591 9.4375 7.35551 9.4375 7.35551C9.4375 7.41963 9.439 7.48375 9.44151 7.54688L9.44301 7.58796C9.44602 7.64908 9.45002 7.7097 9.45503 7.76981L9.45854 7.80638C9.46305 7.85848 9.46856 7.91009 9.47507 7.96119L9.48008 8.00126C9.4871 8.05637 9.49561 8.11198 9.50513 8.16709L9.51515 8.2247C9.52763 8.29497 9.54166 8.36497 9.55723 8.43462L9.58328 8.54483L9.60332 8.62248C9.62771 8.71476 9.65478 8.80632 9.68448 8.89712C10.3478 10.9279 12.2014 12.3251 13.3987 13.0456L13.4072 13.0516C14.5645 13.748 15.5539 14.0911 15.7813 14.1347C16.0744 14.0786 17.6348 13.524 19.1905 12.3557C20.5291 11.3492 22.1251 9.66562 22.1251 7.35552C22.1251 7.35552 22.1248 2.85222 22.1249 2.00018C22.125 1.14813 21.6113 0.536591 21.0654 0.431909ZM1.47105 11.6714C1.62246 11.4615 1.91529 11.414 2.12531 11.5652C2.79752 12.0504 3.70497 12.0504 4.37717 11.5652C4.58732 11.4222 4.87319 11.4726 5.02166 11.679C5.17014 11.8853 5.12715 12.1724 4.92474 12.3262C3.9256 13.0473 2.57689 13.0473 1.57775 12.3262C1.3677 12.1749 1.31993 11.8821 1.47105 11.6719V11.6714ZM17.7388 9.01526H13.8232C14.0411 9.91465 14.8456 10.5485 15.771 10.5497H15.7911C16.7165 10.5487 17.5212 9.91475 17.7388 9.01526L17.7388 9.01526ZM17.2011 4.6227C18.2003 3.90213 19.5485 3.90213 20.5476 4.6227C20.691 4.7176 20.7709 4.88334 20.7559 5.05461C20.7409 5.22588 20.6334 5.37521 20.4757 5.44375C20.3181 5.51229 20.1355 5.48905 20.0001 5.38318C19.328 4.89819 18.4208 4.89819 17.7487 5.38318C17.613 5.4863 17.432 5.50766 17.276 5.43897C17.1201 5.37028 17.0136 5.22237 16.9981 5.05264C16.9825 4.88292 17.0602 4.71811 17.2011 4.6222L17.2011 4.6227ZM11.0141 4.6227C12.0133 3.90189 13.3618 3.90189 14.3611 4.6227C14.5044 4.7176 14.5844 4.88334 14.5694 5.05461C14.5544 5.22588 14.4468 5.37521 14.2892 5.44374C14.1315 5.51228 13.949 5.48904 13.8135 5.38318C13.1414 4.89819 12.2342 4.89819 11.5621 5.38318C11.4263 5.48256 11.2475 5.50148 11.0939 5.43274C10.9402 5.36401 10.8352 5.21817 10.8187 5.05063C10.8022 4.8831 10.8768 4.71958 11.0141 4.6222L11.0141 4.6227Z" fill="#AFB1B7"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 242 KiB |
|
Before Width: | Height: | Size: 207 KiB |
@ -1,3 +0,0 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 0C17.072 0 22 4.928 22 11C22 17.072 17.072 22 11 22C4.928 22 0 17.072 0 11C0 4.928 4.928 0 11 0V0ZM11 5.5C7.964 5.5 5.5 7.964 5.5 11C5.50341 13.4706 7.15302 15.636 9.53393 16.2952C11.9148 16.9545 14.4433 15.946 15.7169 13.8291L13.8304 12.6983C13.0668 13.97 11.5486 14.5759 10.1193 14.1795C8.68994 13.7831 7.70072 12.4817 7.7012 10.9985C7.70168 9.51517 8.69175 8.21446 10.1213 7.81898C11.5509 7.4235 13.0687 8.03044 13.8315 9.3026L15.7169 8.1696C14.7228 6.51286 12.9321 5.49941 11 5.5H11Z" fill="#AFB1B7"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 662 B |
@ -1,24 +0,0 @@
|
||||
<svg width="259" height="69" viewBox="0 0 259 69" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_0_7)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 34.5C0 15.4462 15.4462 0 34.5 0H224.5C243.554 0 259 15.4462 259 34.5C259 53.5538 243.554 69 224.5 69H34.5C15.4462 69 0 53.5538 0 34.5Z" fill="white" fill-opacity="0.74902"/>
|
||||
</g>
|
||||
<circle cx="51.5" cy="34.5" r="7.5" fill="black"/>
|
||||
<circle cx="209.5" cy="34.5" r="18.5" fill="black"/>
|
||||
<circle cx="209.5" cy="34.5" r="5.5" fill="black"/>
|
||||
<circle cx="35.5" cy="34.5" r="4.5" fill="black"/>
|
||||
<circle cx="66.5" cy="34.5" r="4.5" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M115 34.5C115 32.567 116.567 31 118.5 31H141.5C143.433 31 145 32.567 145 34.5C145 36.433 143.433 38 141.5 38H118.5C116.567 38 115 36.433 115 34.5Z" fill="black"/>
|
||||
<rect x="87" y="15" width="4" height="39" fill="#959595" fill-opacity="0.74902"/>
|
||||
<rect x="169" y="15" width="4" height="39" fill="#959595" fill-opacity="0.74902"/>
|
||||
<defs>
|
||||
<filter id="filter0_d_0_7" x="-4" y="-4" width="267" height="77" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.392157 0 0 0 0 0.392157 0 0 0 0 0.392157 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_0_7"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_0_7" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
@ -1,7 +0,0 @@
|
||||
<svg width="85" height="12" viewBox="0 0 85 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="4.5" cy="6.5" r="4.5" fill="#C0C4C8"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 6C15 2.68629 17.6863 0 21 0H34C37.3137 0 40 2.68629 40 6C40 9.31371 37.3137 12 34 12H21C17.6863 12 15 9.31371 15 6Z" fill="#41A4FD"/>
|
||||
<circle cx="50.5" cy="6.5" r="4.5" fill="#C0C4C8"/>
|
||||
<circle cx="65.5" cy="6.5" r="4.5" fill="#C0C4C8"/>
|
||||
<circle cx="80.5" cy="6.5" r="4.5" fill="#C0C4C8"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 497 B |
|
Before Width: | Height: | Size: 1.1 MiB |
@ -1,3 +0,0 @@
|
||||
<svg width="20" height="18" viewBox="0 0 20 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.0559 0H0.945609C0.423363 0 0 0.447715 0 1V17C0 17.552 0.423633 18 0.945609 18H19.0559C19.5764 18 20 17.552 20 17V1C19.9992 0.448095 19.5763 0.000888824 19.0544 0H19.0559ZM4.50451 16H1.8916V13.9968H4.51359V11.9968H1.8916V9.9968H4.51359V7.9968H1.8916V5.9968H4.51359V3.9968H1.8916V2H4.50451V16V16ZM18.1093 3.99519L16.0637 3.99519V5.99519L18.1093 5.99519V7.99519L16.0637 7.99519V9.99519L18.1093 9.99519V11.9952H16.0637V13.9952L18.1093 13.9952V15.9968H16.041V1.9968H18.1093V3.9952V3.99519Z" fill="#AFB1B7"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 659 B |
@ -1,3 +0,0 @@
|
||||
<svg width="23" height="22" viewBox="0 0 23 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 7.6475C0 8.16531 0 14.2805 0 14.2805C0 16.59 1.5961 18.2743 2.9357 19.2803C4.49022 20.4491 6.05075 21.0036 6.34432 21.0597C6.6374 21.0036 8.19793 20.4491 9.75294 19.2803C11.092 18.2743 12.6881 16.59 12.6881 14.2805V13.7094C12.0797 13.3309 11.5066 12.8983 10.9758 12.4169C9.98786 13.0481 8.71463 13.012 7.76407 12.3257C7.56166 12.1719 7.51867 11.8848 7.66715 11.6785C7.81563 11.4721 8.1015 11.4217 8.31164 11.5647C8.89103 11.9827 9.65433 12.0452 10.294 11.727C10.1828 11.6028 10.0761 11.4765 9.97387 11.3483C9.4759 10.7211 9.10268 10.0648 8.85671 9.38445L8.6007 8.44662L8.58267 8.34643L8.58067 8.33541C8.57466 8.29922 8.56899 8.26299 8.56363 8.22671L8.56163 8.21319L8.54661 8.10548L8.5451 8.08944C8.5401 8.05437 8.53608 8.0183 8.53258 7.98274L8.53157 7.97272C8.52026 7.86458 8.5119 7.75616 8.50653 7.64757C8.50653 7.63454 8.50552 7.62202 8.50502 7.60899L8.50302 7.53885L8.50101 7.48124C6.62838 7.62753 4.55635 7.57593 2.85755 7.33295C1.58909 7.1511 1.57376 7.10999 1.07139 6.96036C0.518555 6.79431 0 7.12969 0 7.6475ZM6.35453 15.4708H6.33449C5.40904 15.4719 4.60439 16.1058 4.38672 17.0053H8.3023C8.08447 16.1059 7.27991 15.4721 6.35453 15.4708H6.35453ZM21.0654 0.431909C19.7504 0.192045 18.9529 -0.000125216 15.781 2.63279e-08C12.6091 0.000125268 11.8126 0.236747 10.5555 0.450036C9.8584 0.572662 9.4375 1.21924 9.4375 2C9.4375 3.93591 9.4375 7.35551 9.4375 7.35551C9.4375 7.41963 9.439 7.48375 9.44151 7.54688L9.44301 7.58796C9.44602 7.64908 9.45002 7.7097 9.45503 7.76981L9.45854 7.80638C9.46305 7.85848 9.46856 7.91009 9.47507 7.96119L9.48008 8.00126C9.4871 8.05637 9.49561 8.11198 9.50513 8.16709L9.51515 8.2247C9.52763 8.29497 9.54166 8.36497 9.55723 8.43462L9.58328 8.54483L9.60332 8.62248C9.62771 8.71476 9.65478 8.80632 9.68448 8.89712C10.3478 10.9279 12.2014 12.3251 13.3987 13.0456L13.4072 13.0516C14.5645 13.748 15.5539 14.0911 15.7813 14.1347C16.0744 14.0786 17.6348 13.524 19.1905 12.3557C20.5291 11.3492 22.1251 9.66562 22.1251 7.35552C22.1251 7.35552 22.1248 2.85222 22.1249 2.00018C22.125 1.14813 21.6113 0.536591 21.0654 0.431909ZM1.47105 11.6714C1.62246 11.4615 1.91529 11.414 2.12531 11.5652C2.79752 12.0504 3.70497 12.0504 4.37717 11.5652C4.58732 11.4222 4.87319 11.4726 5.02166 11.679C5.17014 11.8853 5.12715 12.1724 4.92474 12.3262C3.9256 13.0473 2.57689 13.0473 1.57775 12.3262C1.3677 12.1749 1.31993 11.8821 1.47105 11.6719V11.6714ZM17.7388 9.01526H13.8232C14.0411 9.91465 14.8456 10.5485 15.771 10.5497H15.7911C16.7165 10.5487 17.5212 9.91475 17.7388 9.01526L17.7388 9.01526ZM17.2011 4.6227C18.2003 3.90213 19.5485 3.90213 20.5476 4.6227C20.691 4.7176 20.7709 4.88334 20.7559 5.05461C20.7409 5.22588 20.6334 5.37521 20.4757 5.44375C20.3181 5.51229 20.1355 5.48905 20.0001 5.38318C19.328 4.89819 18.4208 4.89819 17.7487 5.38318C17.613 5.4863 17.432 5.50766 17.276 5.43897C17.1201 5.37028 17.0136 5.22237 16.9981 5.05264C16.9825 4.88292 17.0602 4.71811 17.2011 4.6222L17.2011 4.6227ZM11.0141 4.6227C12.0133 3.90189 13.3618 3.90189 14.3611 4.6227C14.5044 4.7176 14.5844 4.88334 14.5694 5.05461C14.5544 5.22588 14.4468 5.37521 14.2892 5.44374C14.1315 5.51228 13.949 5.48904 13.8135 5.38318C13.1414 4.89819 12.2342 4.89819 11.5621 5.38318C11.4263 5.48256 11.2475 5.50148 11.0939 5.43274C10.9402 5.36401 10.8352 5.21817 10.8187 5.05063C10.8022 4.8831 10.8768 4.71958 11.0141 4.6222L11.0141 4.6227Z" fill="#AFB1B7"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.4 KiB |
@ -1,3 +0,0 @@
|
||||
<svg width="26" height="23" viewBox="0 0 26 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.8674 2.08791C22.524 0.748704 20.698 0 18.7465 0C16.7951 0 14.9691 0.748704 13.6048 2.10824L13.0099 2.69215L12.4121 2.10137C11.0409 0.746233 9.21431 0 7.26929 0C5.32428 0 3.49764 0.746232 2.12649 2.10137C0.757832 3.44305 0 5.2478 0 7.17641C0 9.10503 0.757832 10.9098 2.13343 12.258L4.41082 14.5091L11.3269 21.3447L13.0018 23L14.6787 21.3466L21.6106 14.511L23.8871 12.2457C25.2227 10.9477 26 9.09294 26 7.17641C25.9997 5.25988 25.2227 3.40515 23.8674 2.08791L23.8674 2.08791ZM22.2041 10.5942L19.9353 12.8521L13.0034 19.6874L6.0873 12.8518L3.80297 10.5939C1.8938 8.72269 1.8938 5.62982 3.80297 3.75834C4.75756 2.8149 6.01311 2.34332 7.26893 2.34332C8.52476 2.34332 9.78031 2.8149 10.7349 3.75834L12.2378 5.24393H13.7748L15.2883 3.75834C16.2351 2.8149 17.4906 2.34332 18.7465 2.34332C20.0023 2.34332 21.2578 2.8149 22.2044 3.75834C23.159 4.68639 23.6283 5.93881 23.6283 7.17612C23.6283 8.41343 23.1587 9.66612 22.2041 10.5942V10.5942Z" fill="#6D6F74"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,3 +0,0 @@
|
||||
<svg width="26" height="32" viewBox="0 0 26 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21.0631 10.399L18.6311 12.799C18.6311 12.799 18.6311 3.199 10.5181 0C10.5181 0 9.7081 8.8 5.65111 11.998C1.59513 15.2 -6.51884 24.797 9.7041 31.997C9.7041 31.997 1.59113 23.197 12.1361 16.8C12.1361 16.8 11.3261 19.998 15.3821 23.2C19.4391 26.399 15.3821 32 15.3821 32C15.3821 32 34.851 27.199 21.0631 10.399V10.399Z" fill="#FF3419"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 447 B |
@ -1,3 +0,0 @@
|
||||
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.2008 12.9985L10.4014 18.1985V7.79852L18.2008 12.9985L18.2008 12.9985ZM12.9998 23.3985C18.7447 23.3985 23.401 18.7427 23.401 12.9985C23.401 7.25429 18.7447 2.59851 12.9998 2.59851C7.25495 2.59851 2.59863 7.25728 2.59863 12.9985C2.59863 18.7397 7.25495 23.3985 12.9998 23.3985V23.3985ZM13 26C5.81965 26 0 20.181 0 13.0015C0 5.82197 5.81965 0 13 0C20.1804 0 26 5.81898 26 12.9985C26 20.178 20.1804 26 13 26V26Z" fill="#6D6F74"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 582 B |
@ -1,3 +0,0 @@
|
||||
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.2006 12.9985L10.4012 18.1985V7.7985L18.2006 12.9985L18.2006 12.9985ZM13 23.3985C18.7449 23.3985 23.4012 18.7427 23.4012 12.9985C23.4012 7.25429 18.7449 2.5985 13 2.5985C7.25512 2.5985 2.5988 7.25728 2.5988 12.9985C2.5988 18.7397 7.25512 23.3985 13 23.3985V23.3985ZM13 26C5.81965 26 0 20.181 0 13.0015C0 5.82197 5.81965 0 13 0C20.1804 0 26 5.81898 26 12.9985C26 20.178 20.1804 26 13 26V26Z" fill="#6D6F74"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 563 B |
@ -1,3 +0,0 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 0C17.072 0 22 4.928 22 11C22 17.072 17.072 22 11 22C4.928 22 0 17.072 0 11C0 4.928 4.928 0 11 0V0ZM11 5.5C7.964 5.5 5.5 7.964 5.5 11C5.50341 13.4706 7.15302 15.636 9.53393 16.2952C11.9148 16.9545 14.4433 15.946 15.7169 13.8291L13.8304 12.6983C13.0668 13.97 11.5486 14.5759 10.1193 14.1795C8.68994 13.7831 7.70072 12.4817 7.7012 10.9985C7.70168 9.51517 8.69175 8.21446 10.1213 7.81898C11.5509 7.4235 13.0687 8.03044 13.8315 9.3026L15.7169 8.1696C14.7228 6.51286 12.9321 5.49941 11 5.5H11Z" fill="#AFB1B7"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 662 B |
|
Before Width: | Height: | Size: 207 KiB |
|
Before Width: | Height: | Size: 190 KiB |
@ -1,5 +0,0 @@
|
||||
<svg width="23" height="94" viewBox="0 0 23 94" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 7.6475C0 8.16531 0 14.2805 0 14.2805C0 16.59 1.5961 18.2743 2.9357 19.2803C4.49022 20.4491 6.05075 21.0036 6.34432 21.0597C6.6374 21.0036 8.19793 20.4491 9.75294 19.2803C11.092 18.2743 12.6881 16.59 12.6881 14.2805V13.7094C12.0797 13.3309 11.5066 12.8983 10.9758 12.4169C9.98786 13.0481 8.71463 13.012 7.76407 12.3257C7.56166 12.1719 7.51867 11.8848 7.66715 11.6785C7.81563 11.4721 8.1015 11.4217 8.31164 11.5647C8.89103 11.9827 9.65433 12.0452 10.294 11.727C10.1828 11.6028 10.0761 11.4765 9.97387 11.3483C9.4759 10.7211 9.10268 10.0648 8.85671 9.38445L8.6007 8.44662L8.58267 8.34643L8.58067 8.33541C8.57466 8.29922 8.56899 8.26299 8.56363 8.22671L8.56163 8.21319L8.54661 8.10548L8.5451 8.08944C8.5401 8.05437 8.53608 8.0183 8.53258 7.98274L8.53157 7.97272C8.52026 7.86458 8.5119 7.75616 8.50653 7.64757C8.50653 7.63454 8.50552 7.62202 8.50502 7.60899L8.50302 7.53885L8.50101 7.48124C6.62838 7.62753 4.55635 7.57593 2.85755 7.33295C1.58909 7.1511 1.57376 7.10999 1.07139 6.96036C0.518555 6.79431 0 7.12969 0 7.6475ZM6.35453 15.4708H6.33449C5.40904 15.4719 4.60439 16.1058 4.38672 17.0053H8.3023C8.08447 16.1059 7.27991 15.4721 6.35453 15.4708H6.35453ZM21.0654 0.431909C19.7504 0.192045 18.9529 -0.000125216 15.781 2.63281e-08C12.6091 0.000125268 11.8126 0.236747 10.5555 0.450036C9.8584 0.572662 9.4375 1.21924 9.4375 2C9.4375 3.93591 9.4375 7.35551 9.4375 7.35551C9.4375 7.41963 9.439 7.48375 9.44151 7.54688L9.44301 7.58796C9.44602 7.64908 9.45002 7.7097 9.45503 7.76981L9.45854 7.80638C9.46305 7.85848 9.46856 7.91009 9.47507 7.96119L9.48008 8.00126C9.4871 8.05637 9.49561 8.11198 9.50513 8.16709L9.51515 8.2247C9.52763 8.29497 9.54166 8.36497 9.55723 8.43462L9.58328 8.54483L9.60332 8.62248C9.62771 8.71476 9.65478 8.80632 9.68448 8.89712C10.3478 10.9279 12.2014 12.3251 13.3987 13.0456L13.4072 13.0516C14.5645 13.748 15.5539 14.0911 15.7813 14.1347C16.0744 14.0786 17.6348 13.524 19.1905 12.3557C20.5291 11.3492 22.1251 9.66562 22.1251 7.35552C22.1251 7.35552 22.1248 2.85222 22.1249 2.00018C22.125 1.14813 21.6113 0.536591 21.0654 0.431909ZM1.47105 11.6714C1.62246 11.4615 1.91529 11.414 2.12531 11.5652C2.79752 12.0504 3.70497 12.0504 4.37717 11.5652C4.58732 11.4222 4.87319 11.4726 5.02166 11.679C5.17014 11.8853 5.12715 12.1724 4.92474 12.3262C3.9256 13.0473 2.57689 13.0473 1.57775 12.3262C1.3677 12.1749 1.31993 11.8821 1.47105 11.6719V11.6714ZM17.7388 9.01526H13.8232C14.0411 9.91465 14.8456 10.5485 15.771 10.5497H15.7911C16.7165 10.5487 17.5212 9.91475 17.7388 9.01526L17.7388 9.01526ZM17.2011 4.6227C18.2003 3.90213 19.5485 3.90213 20.5476 4.6227C20.691 4.7176 20.7709 4.88334 20.7559 5.05461C20.7409 5.22588 20.6334 5.37521 20.4757 5.44375C20.3181 5.51229 20.1355 5.48905 20.0001 5.38318C19.328 4.89819 18.4208 4.89819 17.7487 5.38318C17.613 5.4863 17.432 5.50766 17.276 5.43897C17.1201 5.37028 17.0136 5.22237 16.9981 5.05264C16.9825 4.88292 17.0602 4.71811 17.2011 4.6222L17.2011 4.6227ZM11.0141 4.6227C12.0133 3.90189 13.3618 3.90189 14.3611 4.6227C14.5044 4.7176 14.5844 4.88334 14.5694 5.05461C14.5544 5.22588 14.4468 5.37521 14.2892 5.44374C14.1315 5.51228 13.949 5.48904 13.8135 5.38318C13.1414 4.89819 12.2342 4.89819 11.5621 5.38318C11.4263 5.48256 11.2475 5.50148 11.0939 5.43274C10.9402 5.36401 10.8352 5.21817 10.8187 5.05063C10.8022 4.8831 10.8768 4.71958 11.0141 4.6222L11.0141 4.6227Z" fill="#AFB1B7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.1184 36H2.00811C1.48586 36 1.0625 36.4477 1.0625 37V53C1.0625 53.552 1.48613 54 2.00811 54H20.1184C20.6389 54 21.0625 53.552 21.0625 53V37C21.0617 36.4481 20.6388 36.0009 20.1169 36H20.1184ZM5.56701 52H2.9541V49.9968H5.57609V47.9968H2.9541V45.9968H5.57609V43.9968H2.9541V41.9968H5.57609V39.9968H2.9541V38H5.56701V52V52ZM19.1718 39.9952L17.1262 39.9952V41.9952L19.1718 41.9952V43.9952L17.1262 43.9952V45.9952L19.1718 45.9952V47.9952H17.1262V49.9952L19.1718 49.9952V51.9968H17.1035V37.9968H19.1718V39.9952V39.9952Z" fill="#AFB1B7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.0625 72C17.1345 72 22.0625 76.928 22.0625 83C22.0625 89.072 17.1345 94 11.0625 94C4.9905 94 0.0625 89.072 0.0625 83C0.0625 76.928 4.9905 72 11.0625 72V72ZM11.0625 77.5C8.0265 77.5 5.5625 79.964 5.5625 83C5.56591 85.4706 7.21552 87.636 9.59643 88.2952C11.9773 88.9545 14.5058 87.946 15.7794 85.8291L13.8929 84.6983C13.1293 85.97 11.6111 86.5759 10.1818 86.1795C8.75244 85.7831 7.76322 84.4817 7.7637 82.9985C7.76418 81.5152 8.75425 80.2145 10.1838 79.819C11.6134 79.4235 13.1312 80.0304 13.894 81.3026L15.7794 80.1696C14.7853 78.5129 12.9946 77.4994 11.0625 77.5H11.0625Z" fill="#AFB1B7"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.6 KiB |
@ -1,3 +0,0 @@
|
||||
<svg width="738" height="2" viewBox="0 0 738 2" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 1H738" stroke="#E1E3E5" stroke-linecap="square"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 165 B |
|
Before Width: | Height: | Size: 227 KiB |
@ -1,3 +0,0 @@
|
||||
<svg width="50" height="38" viewBox="0 0 50 38" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.96056 15.1535C2.5462 14.37 0 11.3188 0 7.67434C0 3.43592 3.44369 0 7.6917 0C10.2552 0 12.5257 1.25119 13.9234 3.17489C15.2911 4.88748 15.6419 6.72398 15.9632 8.40619C16.3281 10.3168 16.6551 12.0285 18.3908 13.1334C21.6289 15.1948 24.5167 13.9049 27.7946 12.4407C30.752 11.1197 34.027 9.65684 38.1634 10.3851C44.8629 11.3863 50.0005 17.1524 50.0005 24.1157C50.0005 31.7838 43.7702 38 36.0847 38C28.7341 38 22.7146 32.3137 22.204 25.1093C22.0847 24.2802 22.0005 23.4596 21.9182 22.657C21.5515 19.0836 21.2212 15.8647 17.9913 13.8281C16.4778 12.8737 14.8768 13.5133 13.0203 14.255C11.0298 15.0502 8.74562 15.9628 5.96056 15.1535Z" fill="#272727"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 800 B |
@ -1,3 +0,0 @@
|
||||
<svg width="26" height="23" viewBox="0 0 26 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.8674 2.08791C22.524 0.748704 20.698 0 18.7465 0C16.7951 0 14.9691 0.748704 13.6048 2.10824L13.0099 2.69215L12.4121 2.10137C11.0409 0.746233 9.21431 0 7.26929 0C5.32428 0 3.49764 0.746232 2.12649 2.10137C0.757832 3.44305 0 5.2478 0 7.17641C0 9.10503 0.757832 10.9098 2.13343 12.258L4.41082 14.5091L11.3269 21.3447L13.0018 23L14.6787 21.3466L21.6106 14.511L23.8871 12.2457C25.2227 10.9477 26 9.09294 26 7.17641C25.9997 5.25988 25.2227 3.40515 23.8674 2.08791L23.8674 2.08791ZM22.2037 10.5942L19.935 12.8521L13.003 19.6874L6.08694 12.8518L3.80261 10.5939C1.89344 8.72267 1.89344 5.62981 3.80261 3.75832C4.75719 2.81489 6.01274 2.34331 7.26857 2.34331C8.5244 2.34331 9.77995 2.81489 10.7345 3.75832L12.2374 5.24392H13.7745L15.2879 3.75832C16.2347 2.81489 17.4903 2.34331 18.7461 2.34331C20.0019 2.34331 21.2575 2.81489 22.204 3.75832C23.1586 4.68637 23.628 5.93879 23.628 7.1761C23.628 8.41342 23.1583 9.66611 22.2037 10.5942V10.5942Z" fill="#6D6F74"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 242 KiB |
|
Before Width: | Height: | Size: 243 KiB |
@ -1,3 +0,0 @@
|
||||
<svg width="21" height="12" viewBox="0 0 21 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.2205 0C19.6854 0 20.0621 0.0872106 20.3507 0.261632C20.6393 0.436053 20.8317 0.665972 20.9279 0.951388C21.024 1.2368 21.024 1.5579 20.9279 1.91467C20.8317 2.27144 20.6233 2.62424 20.3026 2.97309C19.0041 4.40017 17.7536 5.79553 16.5512 7.15919C15.3488 8.52284 14.1865 9.79929 13.0643 10.9885C12.7597 11.3057 12.403 11.5514 11.9942 11.7259C11.5854 11.9003 11.1646 11.9914 10.7317 11.9994C10.2989 12.0073 9.88205 11.9399 9.48126 11.7972C9.08047 11.6545 8.7438 11.4325 8.47127 11.1312C7.84603 10.4494 7.19675 9.75172 6.52342 9.03818L4.45534 6.89756C3.78201 6.18402 3.11269 5.48237 2.44738 4.79262C1.78206 4.10286 1.16084 3.43293 0.583698 2.78281C0.311159 2.48154 0.134811 2.16837 0.0546533 1.84332C-0.0255049 1.51826 -0.0174891 1.22095 0.0787007 0.95139C0.174891 0.681831 0.359255 0.45984 0.631792 0.285419C0.90433 0.110998 1.25703 0.0237872 1.68988 0.0237872C2.25099 0.0237872 3.02452 0.019823 4.01046 0.011895C4.99641 0.00396702 6.07454 3.18977e-06 7.24485 2.83536e-06H10.852H14.3869H17.3448H19.2205V0Z" fill="#41A4FD"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 850 KiB |
24
.gitignore
vendored
@ -32,12 +32,9 @@ douyin_cdp_play_vv_*.txt
|
||||
|
||||
# Chrome profiles and drivers
|
||||
# 注意:Chrome profile 包含大量缓存文件,不应加入Git
|
||||
backend/scripts/config/chrome_profile/
|
||||
backend/drivers/*
|
||||
!backend/drivers/chromedriver.exe
|
||||
|
||||
# Rankings config directory
|
||||
backend/handlers/Rankings/config/
|
||||
scripts/config/chrome_profile/
|
||||
drivers/*
|
||||
!drivers/chromedriver.exe
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
@ -48,16 +45,6 @@ ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Node.js
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.npm
|
||||
.yarn-integrity
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
@ -66,7 +53,4 @@ yarn-error.log*
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Figma 设计文件目录(无需纳入版本控制)
|
||||
.figma/
|
||||
Thumbs.db
|
||||
48
README.md
@ -1,48 +0,0 @@
|
||||
# 榜单系统
|
||||
|
||||
这是一个全栈榜单系统项目,包含后端API服务和前端Vue3应用。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
rank_backend/
|
||||
├── backend/ # 后端代码
|
||||
│ ├── app.py # Flask应用主文件
|
||||
│ ├── config.py # 配置文件
|
||||
│ ├── database.py # 数据库连接
|
||||
│ ├── Timer_worker.py # 定时任务
|
||||
│ ├── handlers/ # 业务处理器
|
||||
│ └── routers/ # 路由定义
|
||||
├── frontend/ # 前端Vue3应用
|
||||
│ ├── src/ # 源代码
|
||||
│ ├── public/ # 静态资源
|
||||
│ └── package.json # 依赖配置
|
||||
└── docs/ # 文档
|
||||
├── API接口文档.md
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 后端服务
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
python3 -m pip install -r ../docs/requirements.txt
|
||||
python3 app.py
|
||||
```
|
||||
|
||||
### 前端应用
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## 开发说明
|
||||
|
||||
- 后端使用Flask框架,提供RESTful API
|
||||
- 前端使用Vue3 + Vite构建
|
||||
- 数据库配置在backend/config.py中
|
||||
- API文档位于docs/API接口文档.md
|
||||
154
Timer_worker.py
Normal file
@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
抖音播放量自动抓取定时器 - 跨平台版本
|
||||
|
||||
功能:
|
||||
- 每晚24:00自动执行抖音播放量抓取任务
|
||||
- 支持Windows、macOS、Linux
|
||||
- 自动保存数据到MongoDB
|
||||
"""
|
||||
|
||||
import schedule
|
||||
import time
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
# 配置日志的函数
|
||||
def setup_logging():
|
||||
"""设置日志配置"""
|
||||
# 确保logs目录存在
|
||||
import os
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
logs_dir = os.path.join(script_dir, 'handlers', 'Rankings', 'logs')
|
||||
os.makedirs(logs_dir, exist_ok=True)
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler(os.path.join(logs_dir, 'scheduler.log'), encoding='utf-8'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
|
||||
class DouyinAutoScheduler:
|
||||
def __init__(self):
|
||||
self.is_running = False
|
||||
|
||||
def run_douyin_scraper(self):
|
||||
"""执行抖音播放量抓取任务"""
|
||||
try:
|
||||
logging.info("🚀 开始执行抖音播放量抓取任务...")
|
||||
|
||||
# 设置环境变量,确保自动模式
|
||||
os.environ['AUTO_CONTINUE'] = '1'
|
||||
|
||||
# 构建脚本路径 - 指向Rankings目录中的脚本
|
||||
script_path = Path(__file__).parent / 'handlers' / 'Rankings' / 'rank_data_scraper.py'
|
||||
|
||||
if not script_path.exists():
|
||||
logging.error(f"❌ 脚本文件不存在: {script_path}")
|
||||
return
|
||||
|
||||
logging.info(f"📁 执行脚本: {script_path}")
|
||||
|
||||
# 使用subprocess执行脚本
|
||||
result = subprocess.run([
|
||||
sys.executable,
|
||||
str(script_path),
|
||||
'--auto',
|
||||
'--duration', '60'
|
||||
], capture_output=True, text=True, encoding='utf-8', errors='ignore')
|
||||
|
||||
if result.returncode == 0:
|
||||
logging.info("✅ 抖音播放量抓取任务执行成功")
|
||||
if result.stdout:
|
||||
logging.info(f"📄 输出: {result.stdout.strip()}")
|
||||
else:
|
||||
logging.error(f"❌ 任务执行失败,返回码: {result.returncode}")
|
||||
if result.stderr:
|
||||
logging.error(f"💥 错误信息: {result.stderr.strip()}")
|
||||
if result.stdout:
|
||||
logging.info(f"📄 输出: {result.stdout.strip()}")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"💥 执行任务时发生异常: {e}")
|
||||
|
||||
def setup_schedule(self):
|
||||
"""设置定时任务"""
|
||||
# 主执行时间:每晚24:00(午夜)
|
||||
schedule.every().day.at("00:00").do(self.run_douyin_scraper)
|
||||
|
||||
logging.info("⏰ 定时器已设置:每晚24:00执行抖音播放量抓取")
|
||||
|
||||
def show_next_run(self):
|
||||
"""显示下次执行时间"""
|
||||
jobs = schedule.get_jobs()
|
||||
if jobs:
|
||||
next_run = jobs[0].next_run
|
||||
logging.info(f"⏰ 下次执行时间: {next_run}")
|
||||
|
||||
def run_once(self):
|
||||
"""立即执行一次"""
|
||||
logging.info("🔧 立即执行模式...")
|
||||
self.run_douyin_scraper()
|
||||
|
||||
def run_test(self):
|
||||
"""测试模式 - 立即执行一次"""
|
||||
logging.info("🧪 测试模式 - 立即执行抖音播放量抓取任务...")
|
||||
self.run_douyin_scraper()
|
||||
|
||||
def start_scheduler(self):
|
||||
"""启动定时器"""
|
||||
self.is_running = True
|
||||
logging.info("🚀 抖音播放量自动抓取定时器已启动")
|
||||
logging.info("⏰ 执行时间:每晚24:00")
|
||||
logging.info("📁 目标脚本:rank_data_scraper.py")
|
||||
logging.info("💾 数据保存:MongoDB")
|
||||
logging.info("⏹️ 按 Ctrl+C 停止定时器")
|
||||
|
||||
self.show_next_run()
|
||||
|
||||
try:
|
||||
while self.is_running:
|
||||
schedule.run_pending()
|
||||
time.sleep(1)
|
||||
|
||||
# 每分钟显示一次状态
|
||||
if int(time.time()) % 60 == 0:
|
||||
self.show_next_run()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logging.info("\n⏹️ 定时器已停止")
|
||||
self.is_running = False
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='抖音播放量自动抓取定时器')
|
||||
parser.add_argument('--test', action='store_true', help='测试模式 - 立即执行一次')
|
||||
parser.add_argument('--once', action='store_true', help='立即执行一次并退出')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 设置日志配置
|
||||
setup_logging()
|
||||
|
||||
scheduler = DouyinAutoScheduler()
|
||||
|
||||
if args.test:
|
||||
scheduler.run_test()
|
||||
elif args.once:
|
||||
scheduler.run_once()
|
||||
else:
|
||||
scheduler.setup_schedule()
|
||||
scheduler.start_scheduler()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
93
app.py
Normal file
@ -0,0 +1,93 @@
|
||||
from flask import Flask
|
||||
from flask_cors import CORS
|
||||
import logging
|
||||
import os
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app) # 允许跨域访问
|
||||
|
||||
# 配置日志
|
||||
# 确保logs目录存在
|
||||
logs_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'handlers', 'Rankings', 'logs')
|
||||
os.makedirs(logs_dir, exist_ok=True)
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler(os.path.join(logs_dir, 'app.log'), encoding='utf-8'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
|
||||
# 导入路由
|
||||
from routers.rank_api_routes import api
|
||||
|
||||
# 注册路由
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""API首页"""
|
||||
from flask import jsonify
|
||||
return jsonify({
|
||||
"name": "抖音播放量数据API服务",
|
||||
"version": "2.0",
|
||||
"description": "主程序服务 - 整合小程序API功能",
|
||||
"endpoints": {
|
||||
"/api/videos": "获取视频列表 (支持分页和排序)",
|
||||
"/api/top": "获取热门视频榜单",
|
||||
"/api/search": "搜索视频",
|
||||
"/api/detail": "获取视频详情",
|
||||
"/api/stats": "获取统计信息",
|
||||
"/api/health": "健康检查"
|
||||
},
|
||||
"features": [
|
||||
"分页支持",
|
||||
"多种排序方式",
|
||||
"搜索功能",
|
||||
"详情查看",
|
||||
"统计分析",
|
||||
"小程序优化"
|
||||
]
|
||||
})
|
||||
|
||||
# 注册小程序API路由
|
||||
@app.route('/api/videos')
|
||||
def get_videos():
|
||||
return api.get_videos()
|
||||
|
||||
@app.route('/api/top')
|
||||
def get_top():
|
||||
return api.get_top()
|
||||
|
||||
@app.route('/api/search')
|
||||
def search():
|
||||
return api.search()
|
||||
|
||||
@app.route('/api/detail')
|
||||
def get_detail():
|
||||
return api.get_detail()
|
||||
|
||||
@app.route('/api/stats')
|
||||
def get_stats():
|
||||
return api.get_stats()
|
||||
|
||||
@app.route('/api/health')
|
||||
def health_check():
|
||||
return api.health_check()
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("启动主程序服务...")
|
||||
print("服务地址: http://localhost:5000")
|
||||
print("API接口列表:")
|
||||
print(" - GET / 显示API信息")
|
||||
print(" - GET /api/videos?page=1&limit=20&sort=playcount 获取视频列表(总播放量排序)")
|
||||
print(" - GET /api/videos?page=1&limit=20&sort=growth 获取视频列表(增长排序,默认昨天到今天的差值)")
|
||||
print(" - GET /api/videos?page=1&limit=20&sort=growth&start_date=2025-10-16&end_date=2025-10-17 获取视频列表(自定义日期范围增长排序)")
|
||||
print(" - GET /api/top?limit=10 获取热门榜单")
|
||||
print(" - GET /api/search?q=关键词&page=1&limit=10 搜索视频")
|
||||
print(" - GET /api/detail?id=视频ID 获取视频详情")
|
||||
print(" - GET /api/stats 获取统计信息")
|
||||
print(" - GET /api/health 健康检查")
|
||||
print("专为小程序优化:分页、搜索、详情、统计、增长排序、自定义日期范围")
|
||||
|
||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||
@ -1,831 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
抖音播放量自动抓取定时器 - 跨平台版本
|
||||
|
||||
功能:
|
||||
- 每晚自动执行抖音播放量抓取任务
|
||||
- 数据抓取完成后自动生类榜单
|
||||
- 支持Windows、macOS、Linux
|
||||
- 自动保存数据到MongoDB
|
||||
|
||||
使用方法:
|
||||
- 正常模式:python Timer_worker.py(启动定时器)
|
||||
- 测试模式:python Timer_worker.py --test(立即执行一次)
|
||||
- 单次执行:python Timer_worker.py --once(立即执行一次并退出)
|
||||
- 仅生成榜单:python Timer_worker.py --ranking-only(仅生成榜单,不抓取数据)
|
||||
"""
|
||||
|
||||
import schedule
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
import argparse
|
||||
from datetime import datetime, date, timedelta
|
||||
import config
|
||||
|
||||
# 添加项目路径到 Python 路径
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), 'handlers', 'Rankings'))
|
||||
from handlers.Rankings.rank_data_scraper import DouyinPlayVVScraper
|
||||
|
||||
|
||||
|
||||
# 配置日志的函数
|
||||
def setup_timer_environment():
|
||||
"""设置定时器相关的环境变量"""
|
||||
config.apply_timer_environment()
|
||||
for key, value in config.TIMER_ENV_CONFIG.items():
|
||||
logging.info(f"设置环境变量: {key}={value}")
|
||||
|
||||
def setup_logging(quiet_mode=False):
|
||||
"""设置日志配置"""
|
||||
# 确保logs目录存在
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
logs_dir = os.path.join(script_dir, 'handlers', 'Rankings', 'logs')
|
||||
os.makedirs(logs_dir, exist_ok=True)
|
||||
|
||||
# 在安静模式下,只记录WARNING及以上级别的日志到控制台
|
||||
console_level = logging.WARNING if quiet_mode else logging.INFO
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO, # 文件日志仍然记录所有INFO级别
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler(os.path.join(logs_dir, 'scheduler.log'), encoding='utf-8'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
|
||||
# 如果是安静模式,调整控制台处理器的级别
|
||||
if quiet_mode:
|
||||
for handler in logging.getLogger().handlers:
|
||||
if isinstance(handler, logging.StreamHandler) and not isinstance(handler, logging.FileHandler):
|
||||
handler.setLevel(console_level)
|
||||
|
||||
class DouyinAutoScheduler:
|
||||
def __init__(self):
|
||||
self.is_running = False
|
||||
# 创建logger实例
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
def _sync_episode_details_with_lock(self, episode_details, comments_summary):
|
||||
"""
|
||||
同步 episode_details 时处理评论锁定逻辑
|
||||
如果有 comments_summary,则保留评论内容,只更新互动数据
|
||||
|
||||
Args:
|
||||
episode_details: 管理数据库中的 episode_details
|
||||
comments_summary: 评论总结字段
|
||||
|
||||
Returns:
|
||||
处理后的 episode_details
|
||||
"""
|
||||
# 如果没有 comments_summary 或没有 episode_details,直接返回原数据
|
||||
if not comments_summary or not episode_details:
|
||||
return episode_details
|
||||
|
||||
# 如果有 comments_summary,说明评论内容已锁定,直接返回管理数据库的数据
|
||||
# 因为管理数据库中已经保存了锁定的评论内容
|
||||
logging.info(f'🔒 检测到 comments_summary,episode_details 将保持锁定状态(包含评论内容)')
|
||||
return episode_details
|
||||
|
||||
def _normalize_play_vv(self, play_vv):
|
||||
"""标准化播放量数据类型,将字符串转换为数字"""
|
||||
if isinstance(play_vv, str):
|
||||
try:
|
||||
return int(play_vv.replace(',', '').replace('万', '0000').replace('亿', '00000000'))
|
||||
except:
|
||||
return 0
|
||||
elif not isinstance(play_vv, (int, float)):
|
||||
return 0
|
||||
return play_vv
|
||||
|
||||
def check_browser_login_status(self):
|
||||
"""检查浏览器登录状态,如果没有登录则提示用户登录"""
|
||||
try:
|
||||
import os
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
profile_dir = os.path.join(script_dir, 'config', 'chrome_profile_timer', 'douyin_persistent')
|
||||
|
||||
|
||||
# 检查配置文件是否为空(可能未登录)
|
||||
import glob
|
||||
profile_files = glob.glob(os.path.join(profile_dir, "*"))
|
||||
if len(profile_files) < 5: # 如果文件太少,可能未登录
|
||||
print("⚠️ 检测到定时器浏览器可能未登录")
|
||||
print(" 请在浏览器中完成抖音登录,并导航到【我的】→【收藏】→【合集】页面")
|
||||
print(" 完成后按回车键继续...")
|
||||
input()
|
||||
else:
|
||||
print("✅ 定时器浏览器已配置,继续执行...")
|
||||
|
||||
except Exception as e:
|
||||
logging.warning(f"检查浏览器登录状态时出错: {e}")
|
||||
print("⚠️ 检查浏览器状态失败,请确保浏览器已正确配置")
|
||||
print(" 完成后按回车键继续...")
|
||||
input()
|
||||
|
||||
def _cleanup_chrome_processes(self):
|
||||
"""清理可能占用配置文件的Chrome进程"""
|
||||
try:
|
||||
import psutil
|
||||
import os
|
||||
|
||||
# 获取当前配置文件路径
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
profile_dir = os.path.join(script_dir, 'config', 'chrome_profile_timer', 'douyin_persistent')
|
||||
|
||||
# 查找使用该配置文件的Chrome进程
|
||||
killed_processes = []
|
||||
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
|
||||
try:
|
||||
if proc.info['name'] and 'chrome' in proc.info['name'].lower():
|
||||
cmdline = proc.info['cmdline']
|
||||
if cmdline and any(profile_dir in arg for arg in cmdline):
|
||||
proc.terminate()
|
||||
killed_processes.append(proc.info['pid'])
|
||||
logging.info(f'终止占用配置文件的Chrome进程: PID {proc.info["pid"]}')
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
||||
continue
|
||||
|
||||
# 等待进程终止
|
||||
if killed_processes:
|
||||
import time
|
||||
time.sleep(2)
|
||||
|
||||
return len(killed_processes) > 0
|
||||
|
||||
except ImportError:
|
||||
# 如果没有psutil,跳过清理以避免影响其他脚本实例
|
||||
logging.warning('psutil 不可用,跳过进程清理(避免全局终止 Chrome)')
|
||||
return False
|
||||
except Exception as e:
|
||||
logging.warning(f'清理Chrome进程时出错: {e}')
|
||||
return False
|
||||
|
||||
def run_douyin_scraper(self):
|
||||
"""执行抖音播放量抓取任务"""
|
||||
try:
|
||||
logging.warning("🚀 开始执行抖音播放量抓取任务...")
|
||||
|
||||
# 设置环境变量,确保定时器模式和自动模式
|
||||
setup_timer_environment()
|
||||
|
||||
# 直接创建并运行 DouyinPlayVVScraper 实例
|
||||
scraper = DouyinPlayVVScraper(
|
||||
start_url="https://www.douyin.com/user/self?showTab=favorite_collection&showSubTab=compilation",
|
||||
auto_continue=True,
|
||||
duration_s=60 # 增加到60秒,给更多时间收集数据
|
||||
)
|
||||
|
||||
print("开始执行抓取任务...")
|
||||
logging.info("📁 开始执行抓取任务...")
|
||||
scraper.run()
|
||||
|
||||
print("抖音播放量抓取任务执行成功")
|
||||
logging.info("✅ 抖音播放量抓取任务执行成功")
|
||||
|
||||
# 数据抓取完成后,自动生成当日榜单
|
||||
self.generate_daily_rankings()
|
||||
|
||||
# 任务完成后立即显示下次执行时间
|
||||
print("🎯 任务完成,准备下次执行...")
|
||||
self.show_next_run()
|
||||
print("💡 定时器正在等待中,将在整点自动执行任务...")
|
||||
|
||||
logging.info("🎯 任务完成,准备下次执行...")
|
||||
logging.info("💡 定时器正在等待中,将在整点自动执行任务...")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"💥 执行任务时发生异常: {e}")
|
||||
import traceback
|
||||
logging.error(f"详细错误信息: {traceback.format_exc()}")
|
||||
|
||||
def generate_daily_rankings(self):
|
||||
"""生成每日榜单数据(基于时间轴对比)"""
|
||||
try:
|
||||
from database import db
|
||||
from datetime import timedelta
|
||||
|
||||
# 获取集合
|
||||
douyin_collection = db['Ranking_storage_list'] # 使用定时器抓取的数据
|
||||
rankings_collection = db['Ranking_storage']
|
||||
|
||||
today = date.today()
|
||||
yesterday = today - timedelta(days=1)
|
||||
today_str = today.strftime('%Y-%m-%d')
|
||||
yesterday_str = yesterday.strftime('%Y-%m-%d')
|
||||
|
||||
logging.info(f"📅 正在生成 {today_str} 的榜单(对比 {yesterday_str})...")
|
||||
|
||||
# 删除当天已有的榜单数据
|
||||
rankings_collection.delete_many({"date": today_str})
|
||||
print(f"🗑️ 已清理 {today_str} 的旧榜单数据")
|
||||
logging.info(f"🗑️ 已清理 {today_str} 的旧榜单数据")
|
||||
|
||||
# 获取今天和昨天的榜单数据进行对比
|
||||
try:
|
||||
print("🔄 正在生成时间轴对比榜单...")
|
||||
logging.info("🔄 正在生成时间轴对比榜单...")
|
||||
|
||||
# 获取最新批次的数据
|
||||
latest_batch = douyin_collection.find_one(sort=[("batch_time", -1)])
|
||||
if not latest_batch:
|
||||
logging.warning("⚠️ 未找到任何数据")
|
||||
return False
|
||||
|
||||
latest_batch_time = latest_batch.get("batch_time")
|
||||
logging.info(f"📊 找到最新批次时间: {latest_batch_time}")
|
||||
|
||||
# 只获取最新批次的数据
|
||||
today_videos_raw = list(douyin_collection.find({"batch_time": latest_batch_time}).sort("play_vv", -1))
|
||||
logging.info(f"📊 最新批次数据数量: {len(today_videos_raw)}")
|
||||
|
||||
# 按短剧ID去重,每个短剧只保留播放量最高的一条
|
||||
# 🚫 过滤掉空的或无效的mix_id和播放量为0的记录
|
||||
unique_videos = {}
|
||||
for video in today_videos_raw:
|
||||
mix_id = video.get("mix_id", "").strip()
|
||||
mix_name = video.get("mix_name", "").strip()
|
||||
play_vv = video.get("play_vv", 0)
|
||||
|
||||
# 过滤掉空的或无效的mix_id
|
||||
if not mix_id or mix_id == "" or mix_id.lower() == "null":
|
||||
continue
|
||||
|
||||
# 注意:播放量为0的数据也会被保留,可能是新发布的短剧
|
||||
if play_vv <= 0:
|
||||
logging.warning(f"⚠️ 发现播放量为0的数据: mix_name={mix_name}, play_vv={play_vv},仍会保留")
|
||||
|
||||
if mix_id not in unique_videos or play_vv > unique_videos[mix_id].get("play_vv", 0):
|
||||
unique_videos[mix_id] = video
|
||||
|
||||
today_videos = list(unique_videos.values())
|
||||
|
||||
logging.info(f"📊 今日数据去重后:{len(today_videos)} 个独特短剧(原始数据:{len(today_videos_raw)} 条)")
|
||||
|
||||
# 从Ranking_storage_list中获取昨天最后一次抓取的数据
|
||||
yesterday_start = datetime.combine(yesterday, datetime.min.time())
|
||||
yesterday_end = datetime.combine(yesterday, datetime.max.time())
|
||||
|
||||
# 获取昨天的最后一次抓取数据(按batch_time排序取最新的)
|
||||
yesterday_latest_batch = douyin_collection.find_one({
|
||||
"batch_time": {
|
||||
"$gte": yesterday_start,
|
||||
"$lte": yesterday_end
|
||||
}
|
||||
}, sort=[("batch_time", -1)])
|
||||
|
||||
yesterday_data = {}
|
||||
if yesterday_latest_batch:
|
||||
yesterday_batch_time = yesterday_latest_batch.get("batch_time")
|
||||
logging.info(f"📊 找到昨天最后一次抓取时间: {yesterday_batch_time}")
|
||||
|
||||
# 获取昨天最后一次抓取的所有数据
|
||||
yesterday_videos_raw = list(douyin_collection.find({
|
||||
"batch_time": yesterday_batch_time
|
||||
}).sort("play_vv", -1))
|
||||
|
||||
# 按短剧ID去重,每个短剧只保留播放量最高的一条
|
||||
# 🚫 过滤掉空的或无效的mix_id
|
||||
unique_yesterday_videos = {}
|
||||
for video in yesterday_videos_raw:
|
||||
mix_id = video.get("mix_id", "").strip()
|
||||
mix_name = video.get("mix_name", "").strip()
|
||||
play_vv = video.get("play_vv", 0)
|
||||
|
||||
# 过滤掉空的或无效的mix_id
|
||||
if not mix_id or mix_id == "" or mix_id.lower() == "null":
|
||||
continue
|
||||
|
||||
# 注意:播放量为0的数据也会被保留,可能是新发布的短剧
|
||||
if play_vv <= 0:
|
||||
logging.warning(f"⚠️ 昨天数据中发现播放量为0: mix_name={mix_name}, play_vv={play_vv},仍会保留")
|
||||
|
||||
if mix_id not in unique_yesterday_videos or play_vv > unique_yesterday_videos[mix_id].get("play_vv", 0):
|
||||
unique_yesterday_videos[mix_id] = video
|
||||
|
||||
# 将昨天的数据转换为字典,以短剧ID为键
|
||||
for mix_id, video in unique_yesterday_videos.items():
|
||||
yesterday_data[mix_id] = {
|
||||
"rank": 0, # 原始数据没有排名,设为0
|
||||
"play_vv": video.get("play_vv", 0),
|
||||
"video_id": str(video.get("_id", ""))
|
||||
}
|
||||
|
||||
logging.info(f"📊 找到昨天的原始数据,共 {len(yesterday_data)} 个短剧(原始数据:{len(yesterday_videos_raw)} 条)")
|
||||
else:
|
||||
logging.info("📊 未找到昨天的原始数据,将作为首次生成")
|
||||
|
||||
if today_videos:
|
||||
# 先计算所有视频的播放量差值
|
||||
videos_with_growth = []
|
||||
for video in today_videos:
|
||||
video_id = str(video.get("_id", ""))
|
||||
current_play_vv = video.get("play_vv", 0)
|
||||
|
||||
# 计算与昨天的对比数据
|
||||
play_vv_change = 0
|
||||
play_vv_change_rate = 0
|
||||
is_new = True
|
||||
|
||||
mix_id = video.get("mix_id", "")
|
||||
if mix_id in yesterday_data:
|
||||
is_new = False
|
||||
yesterday_play_vv = yesterday_data[mix_id]["play_vv"]
|
||||
|
||||
# 计算播放量变化
|
||||
play_vv_change = current_play_vv - yesterday_play_vv
|
||||
if yesterday_play_vv > 0:
|
||||
play_vv_change_rate = round((play_vv_change / yesterday_play_vv) * 100, 2)
|
||||
|
||||
# 创建包含增长数据的视频项
|
||||
video_with_growth = {
|
||||
"video": video,
|
||||
"play_vv_change": play_vv_change,
|
||||
"play_vv_change_rate": play_vv_change_rate,
|
||||
"is_new": is_new,
|
||||
"yesterday_data": yesterday_data.get(mix_id, {})
|
||||
}
|
||||
videos_with_growth.append(video_with_growth)
|
||||
|
||||
# 按播放量差值降序排序(差值越大排名越靠前)
|
||||
videos_with_growth.sort(key=lambda x: x["play_vv_change"], reverse=True)
|
||||
|
||||
comprehensive_ranking = {
|
||||
"date": today_str,
|
||||
"type": "comprehensive",
|
||||
"name": "播放量增长榜单",
|
||||
"description": f"基于 {yesterday_str} 和 {today_str} 播放量差值排序的榜单(差值越大排名越靠前)",
|
||||
"comparison_date": yesterday_str,
|
||||
"total_videos": len(videos_with_growth),
|
||||
"data": []
|
||||
}
|
||||
|
||||
# 获取Rankings_management集合用于补充详细信息
|
||||
rankings_management_collection = db['Rankings_management']
|
||||
|
||||
# 生成排序后的榜单数据
|
||||
rank = 1 # 使用独立的排名计数器
|
||||
for item in videos_with_growth:
|
||||
video = item["video"]
|
||||
video_id = str(video.get("_id", ""))
|
||||
current_play_vv = video.get("play_vv", 0)
|
||||
mix_name = video.get("mix_name", "").strip()
|
||||
|
||||
# 🚫 跳过无效数据:确保mix_name不为空
|
||||
# 注意:播放量为0的数据也会被保留,可能是新发布的短剧
|
||||
if not mix_name or mix_name == "" or mix_name.lower() == "null":
|
||||
self.logger.warning(f"跳过空的mix_name记录,video_id: {video_id}")
|
||||
continue
|
||||
|
||||
if current_play_vv <= 0:
|
||||
self.logger.warning(f"⚠️ 榜单中发现播放量为0的记录: mix_name={mix_name}, play_vv={current_play_vv},仍会保留")
|
||||
|
||||
# 计算排名变化(基于昨天的排名)
|
||||
rank_change = 0
|
||||
if not item["is_new"] and item["yesterday_data"]:
|
||||
yesterday_rank = item["yesterday_data"].get("rank", 0)
|
||||
rank_change = yesterday_rank - rank # 使用当前排名计数器
|
||||
|
||||
# 🔍 从Rankings_management获取详细信息(按mix_id查询,因为管理数据库每个短剧只有一条记录)
|
||||
mix_id = video.get("mix_id", "").strip()
|
||||
management_data = None
|
||||
|
||||
if mix_id:
|
||||
# 直接按mix_id查询,不需要按日期查询
|
||||
management_data = rankings_management_collection.find_one({"mix_id": mix_id})
|
||||
if management_data:
|
||||
logging.info(f"📋 从 Rankings_management 获取数据: {mix_name} (mix_id: {mix_id})")
|
||||
else:
|
||||
logging.warning(f"⚠️ 未找到管理数据: {mix_name} (mix_id: {mix_id})")
|
||||
else:
|
||||
logging.warning(f"⚠️ mix_id 为空: {mix_name}")
|
||||
|
||||
ranking_item = {
|
||||
# 🎯 核心榜单字段
|
||||
"rank": rank, # 使用排名计数器
|
||||
"title": mix_name,
|
||||
"mix_name": mix_name,
|
||||
"play_vv": current_play_vv,
|
||||
"series_author": video.get("series_author", ""),
|
||||
"video_id": video_id,
|
||||
"video_url": video.get("video_url", ""),
|
||||
"cover_image_url": video.get("cover_image_url", ""),
|
||||
"playcount_str": video.get("playcount", ""),
|
||||
|
||||
# 📋 从Rankings_management获取的详细字段
|
||||
"batch_id": management_data.get("batch_id", "") if management_data else "",
|
||||
"batch_time": management_data.get("batch_time") if management_data else None,
|
||||
"item_sequence": management_data.get("item_sequence", 0) if management_data else 0,
|
||||
"mix_id": video.get("mix_id", ""), # 直接从原始数据获取mix_id
|
||||
"playcount": management_data.get("playcount", "") if management_data else "",
|
||||
"request_id": management_data.get("request_id", "") if management_data else "",
|
||||
"cover_image_url_original": management_data.get("cover_image_url_original", "") if management_data else "",
|
||||
"cover_upload_success": management_data.get("cover_upload_success", True) if management_data else True,
|
||||
"cover_backup_urls": management_data.get("cover_backup_urls", []) if management_data else [],
|
||||
"desc": management_data.get("desc", "") if management_data else "",
|
||||
"updated_to_episode": management_data.get("updated_to_episode", 0) if management_data else 0,
|
||||
"episode_video_ids": management_data.get("episode_video_ids", []) if management_data else [],
|
||||
# 🔒 episode_details 同步逻辑:如果有 comments_summary,保留评论内容但更新互动数据
|
||||
"episode_details": self._sync_episode_details_with_lock(
|
||||
management_data.get("episode_details", []) if management_data else [],
|
||||
management_data.get("comments_summary", "") if management_data else ""
|
||||
),
|
||||
"data_status": management_data.get("data_status", "") if management_data else "",
|
||||
"realtime_saved": management_data.get("realtime_saved", True) if management_data else True,
|
||||
"created_at": management_data.get("created_at") if management_data else None,
|
||||
"last_updated": management_data.get("last_updated") if management_data else None,
|
||||
# 🎬 评论总结字段:直接从管理数据库获取(按mix_id查询)
|
||||
"comments_summary": management_data.get("comments_summary", "") if management_data else "",
|
||||
|
||||
# 🔑 分类字段:直接从管理数据库获取(按mix_id查询,每个短剧只有一条记录)
|
||||
"Manufacturing_Field": management_data.get("Manufacturing_Field", "") if management_data else "",
|
||||
"Copyright_field": management_data.get("Copyright_field", "") if management_data else "",
|
||||
"classification_type": management_data.get("classification_type", "") if management_data else "",
|
||||
"release_date": management_data.get("release_date", "") if management_data else "",
|
||||
"Novel_IDs": management_data.get("Novel_IDs", []) if management_data else [],
|
||||
"Anime_IDs": management_data.get("Anime_IDs", []) if management_data else [],
|
||||
"Drama_IDs": management_data.get("Drama_IDs", []) if management_data else [],
|
||||
|
||||
# 🔒 锁定状态:直接从管理数据库获取
|
||||
"field_lock_status": management_data.get("field_lock_status", {}) if management_data else {},
|
||||
|
||||
# 📊 时间轴对比数据(重要:包含播放量差值)
|
||||
"timeline_data": {
|
||||
"is_new": item["is_new"],
|
||||
"rank_change": rank_change,
|
||||
"play_vv_change": item["play_vv_change"],
|
||||
"play_vv_change_rate": item["play_vv_change_rate"],
|
||||
"yesterday_rank": item["yesterday_data"].get("rank", 0) if not item["is_new"] else 0,
|
||||
"yesterday_play_vv": item["yesterday_data"].get("play_vv", 0) if not item["is_new"] else 0
|
||||
}
|
||||
}
|
||||
|
||||
comprehensive_ranking["data"].append(ranking_item)
|
||||
rank += 1 # 递增排名计数器
|
||||
|
||||
# 为每次计算添加唯一的时间戳,确保数据唯一性
|
||||
current_timestamp = datetime.now()
|
||||
comprehensive_ranking["created_at"] = current_timestamp
|
||||
comprehensive_ranking["calculation_id"] = f"{today_str}_{current_timestamp.strftime('%H%M%S')}"
|
||||
|
||||
# 检查今天已有多少次计算
|
||||
existing_count = rankings_collection.count_documents({
|
||||
"date": today_str,
|
||||
"type": "comprehensive"
|
||||
})
|
||||
|
||||
comprehensive_ranking["calculation_sequence"] = existing_count + 1
|
||||
|
||||
# 总是插入新的榜单记录,保留所有历史计算数据
|
||||
rankings_collection.insert_one(comprehensive_ranking)
|
||||
logging.info(f"📝 创建了新的今日榜单数据(第{existing_count + 1}次计算,包含最新差值)")
|
||||
logging.info(f"🔖 计算ID: {comprehensive_ranking['calculation_id']}")
|
||||
|
||||
# 📊 检查数据完整性:统计从Rankings_management成功获取详细信息的项目数量
|
||||
total_items = len(comprehensive_ranking["data"])
|
||||
items_with_management_data = 0
|
||||
items_with_manufacturing = 0
|
||||
items_with_copyright = 0
|
||||
|
||||
for item in comprehensive_ranking["data"]:
|
||||
# 检查是否从Rankings_management获取到了数据
|
||||
if item.get("batch_id") or item.get("desc") or item.get("Manufacturing_Field") or item.get("Copyright_field"):
|
||||
items_with_management_data += 1
|
||||
if item.get("Manufacturing_Field"):
|
||||
items_with_manufacturing += 1
|
||||
if item.get("Copyright_field"):
|
||||
items_with_copyright += 1
|
||||
|
||||
print(f"数据完整性统计:")
|
||||
print(f" 总项目数: {total_items}")
|
||||
print(f" 从Rankings_management获取到详细信息: {items_with_management_data}")
|
||||
print(f" 包含Manufacturing_Field: {items_with_manufacturing}")
|
||||
print(f" 包含Copyright_field: {items_with_copyright}")
|
||||
|
||||
logging.info(f"📊 数据完整性: 总{total_items}项,获取详细信息{items_with_management_data}项,Manufacturing_Field: {items_with_manufacturing},Copyright_field: {items_with_copyright}")
|
||||
|
||||
# 统计信息
|
||||
new_count = sum(1 for item in comprehensive_ranking["data"] if item["timeline_data"]["is_new"])
|
||||
print(f"✅ 时间轴对比榜单生成成功")
|
||||
print(f"📊 总计 {len(comprehensive_ranking['data'])} 条记录")
|
||||
print(f"🆕 新上榜 {new_count} 条")
|
||||
print(f"🔄 对比基准日期: {yesterday_str}")
|
||||
|
||||
logging.info(f"✅ 时间轴对比榜单生成成功")
|
||||
logging.info(f"📊 总计 {len(comprehensive_ranking['data'])} 条记录")
|
||||
logging.info(f"🆕 新上榜 {new_count} 条")
|
||||
logging.info(f"🔄 对比基准日期: {yesterday_str}")
|
||||
|
||||
return True
|
||||
else:
|
||||
logging.warning("⚠️ 榜单生成失败:无今日数据")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"💥 生成时间轴对比榜单时发生异常: {e}")
|
||||
import traceback
|
||||
logging.error(f"详细错误信息: {traceback.format_exc()}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"💥 生成榜单时发生异常: {e}")
|
||||
import traceback
|
||||
logging.error(f"详细错误信息: {traceback.format_exc()}")
|
||||
|
||||
def check_and_sync_missing_fields(self):
|
||||
"""实时检查并同步当天缺失字段"""
|
||||
try:
|
||||
from database import db
|
||||
|
||||
# 只检查当天的数据
|
||||
today = date.today()
|
||||
today_str = today.strftime('%Y-%m-%d')
|
||||
|
||||
# 首先检查 Rankings_management 是否有当天的数据
|
||||
rankings_management_collection = db['Rankings_management']
|
||||
management_count = rankings_management_collection.count_documents({})
|
||||
|
||||
if management_count == 0:
|
||||
# Rankings_management 没有数据,说明还没有抓取,直接返回
|
||||
return
|
||||
|
||||
rankings_collection = db['Ranking_storage']
|
||||
key_fields = ['Manufacturing_Field', 'Copyright_field', 'desc', 'series_author']
|
||||
|
||||
# 检查今天是否有缺失字段的数据
|
||||
missing_conditions = []
|
||||
for field in key_fields:
|
||||
missing_conditions.extend([
|
||||
{field: {"$exists": False}},
|
||||
{field: None},
|
||||
{field: ""}
|
||||
])
|
||||
|
||||
today_missing_count = rankings_collection.count_documents({
|
||||
"date": today_str,
|
||||
"$or": missing_conditions
|
||||
})
|
||||
|
||||
# 如果今天没有缺失数据,静默返回
|
||||
if today_missing_count == 0:
|
||||
return
|
||||
|
||||
logging.info(f"🔍 检测到今天有 {today_missing_count} 条缺失字段,Rankings_management有 {management_count} 条数据,开始实时同步...")
|
||||
|
||||
# 只处理当天的数据
|
||||
dates_to_check = [today_str]
|
||||
|
||||
total_missing = 0
|
||||
total_synced = 0
|
||||
|
||||
for check_date in dates_to_check:
|
||||
# 查询该日期缺失字段的数据
|
||||
rankings_collection = db['Ranking_storage']
|
||||
|
||||
# 检查多个关键字段(包括新增的分类字段)
|
||||
key_fields = ['Manufacturing_Field', 'Copyright_field', 'desc', 'series_author', 'Novel_IDs', 'Anime_IDs', 'Drama_IDs']
|
||||
missing_conditions = []
|
||||
|
||||
for field in key_fields:
|
||||
missing_conditions.extend([
|
||||
{field: {"$exists": False}},
|
||||
{field: None},
|
||||
{field: ""}
|
||||
])
|
||||
|
||||
missing_query = {
|
||||
"date": check_date,
|
||||
"$or": missing_conditions
|
||||
}
|
||||
|
||||
missing_count = rankings_collection.count_documents(missing_query)
|
||||
|
||||
# 详细统计每个字段的缺失情况
|
||||
field_stats = {}
|
||||
total_items = rankings_collection.count_documents({"date": check_date})
|
||||
|
||||
for field in key_fields:
|
||||
missing_field_count = rankings_collection.count_documents({
|
||||
"date": check_date,
|
||||
"$or": [
|
||||
{field: {"$exists": False}},
|
||||
{field: None},
|
||||
{field: ""}
|
||||
]
|
||||
})
|
||||
field_stats[field] = {
|
||||
"missing": missing_field_count,
|
||||
"completion_rate": ((total_items - missing_field_count) / total_items * 100) if total_items > 0 else 0
|
||||
}
|
||||
|
||||
if missing_count > 0:
|
||||
logging.info(f"📅 今日({check_date}): 发现 {missing_count} 条记录缺失字段(总计 {total_items} 条)")
|
||||
|
||||
# 输出详细的字段统计
|
||||
for field, stats in field_stats.items():
|
||||
if stats["missing"] > 0:
|
||||
logging.info(f" - {field}: 缺失 {stats['missing']} 条 ({stats['completion_rate']:.1f}% 完整)")
|
||||
|
||||
total_missing += missing_count
|
||||
|
||||
# 尝试同步
|
||||
try:
|
||||
from routers.rank_api_routes import sync_ranking_storage_fields
|
||||
|
||||
# 使用改进的重试机制
|
||||
sync_result = sync_ranking_storage_fields(
|
||||
target_date=check_date,
|
||||
force_update=False,
|
||||
max_retries=2, # 定期检查时重试2次
|
||||
retry_delay=15 # 15秒重试间隔
|
||||
)
|
||||
|
||||
if sync_result.get("success", False):
|
||||
stats = sync_result.get("stats", {})
|
||||
synced = stats.get("updated_items", 0)
|
||||
retry_count = stats.get("retry_count", 0)
|
||||
pending_final = stats.get("pending_items_final", 0)
|
||||
|
||||
total_synced += synced
|
||||
if synced > 0:
|
||||
logging.info(f"✅ 今日({check_date}): 成功同步 {synced} 条记录")
|
||||
|
||||
if retry_count > 0:
|
||||
logging.info(f"🔄 今日({check_date}): 使用了 {retry_count} 次重试")
|
||||
|
||||
if pending_final > 0:
|
||||
logging.warning(f"⚠️ 今日({check_date}): {pending_final} 条记录在 Rankings_management 中仍未找到")
|
||||
else:
|
||||
logging.warning(f"⚠️ 今日({check_date}): 同步失败 - {sync_result.get('message', '')}")
|
||||
|
||||
except Exception as sync_error:
|
||||
logging.error(f"💥 今日({check_date}): 同步过程出错 - {sync_error}")
|
||||
else:
|
||||
if total_items > 0:
|
||||
logging.info(f"📅 {check_date}: 所有字段完整(总计 {total_items} 条记录)")
|
||||
# 显示完整性统计
|
||||
for field, stats in field_stats.items():
|
||||
logging.info(f" - {field}: {stats['completion_rate']:.1f}% 完整")
|
||||
else:
|
||||
logging.info(f"📅 {check_date}: 无数据")
|
||||
|
||||
if total_missing > 0:
|
||||
logging.info(f"🔍 当天同步完成:发现 {total_missing} 条缺失记录,成功同步 {total_synced} 条")
|
||||
print(f"🔍 当天字段同步:发现 {total_missing} 条缺失,同步 {total_synced} 条")
|
||||
else:
|
||||
# 当天没有缺失数据时,不输出日志(静默模式)
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"💥 检查缺失字段时发生异常: {e}")
|
||||
import traceback
|
||||
logging.error(f"详细错误信息: {traceback.format_exc()}")
|
||||
|
||||
def setup_schedule(self):
|
||||
"""设置定时任务"""
|
||||
# 每小时的整点执行抖音播放量抓取
|
||||
schedule.every().hour.at(":00").do(self.run_douyin_scraper)
|
||||
|
||||
# 每1分钟检查一次缺失字段并尝试同步(实时同步)
|
||||
schedule.every(1).minutes.do(self.check_and_sync_missing_fields)
|
||||
|
||||
logging.info(f"⏰ 定时器已设置:每小时整点执行抖音播放量抓取")
|
||||
logging.info(f"⏰ 定时器已设置:每1分钟检查缺失字段并同步(实时模式)")
|
||||
|
||||
def show_next_run(self):
|
||||
"""显示下次执行时间"""
|
||||
jobs = schedule.get_jobs()
|
||||
if jobs:
|
||||
next_run = jobs[0].next_run
|
||||
current_time = datetime.now()
|
||||
wait_seconds = (next_run - current_time).total_seconds()
|
||||
wait_minutes = int(wait_seconds // 60)
|
||||
wait_hours = int(wait_minutes // 60)
|
||||
remaining_minutes = wait_minutes % 60
|
||||
|
||||
print(f"💡 定时器运行中,下次执行:{next_run.strftime('%Y-%m-%d %H:%M:%S')} (还有{wait_hours}h{remaining_minutes}m)")
|
||||
print(f"⏳ 距离下次执行:{wait_minutes} 分钟 ({int(wait_seconds)} 秒)")
|
||||
|
||||
logging.info(f"💡 定时器运行中,下次执行:{next_run.strftime('%Y-%m-%d %H:%M:%S')} (还有{wait_hours}h{remaining_minutes}m)")
|
||||
logging.info(f"⏳ 距离下次执行:{wait_minutes} 分钟 ({int(wait_seconds)} 秒)")
|
||||
|
||||
def run_once(self):
|
||||
"""立即执行一次"""
|
||||
logging.info("🔧 立即执行模式...")
|
||||
self.run_douyin_scraper()
|
||||
|
||||
def run_test(self):
|
||||
"""测试模式 - 立即执行一次"""
|
||||
logging.info("🧪 测试模式 - 立即执行抖音播放量抓取任务...")
|
||||
self.run_douyin_scraper()
|
||||
|
||||
def run_ranking_only(self):
|
||||
"""仅生成榜单(不抓取数据)"""
|
||||
logging.info("📊 仅生成榜单模式...")
|
||||
self.generate_daily_rankings()
|
||||
|
||||
def start_scheduler(self):
|
||||
"""启动定时器"""
|
||||
self.is_running = True
|
||||
last_status_time = int(time.time()) # 设置为当前时间,1分钟后开始显示状态
|
||||
|
||||
print("🚀 抖音播放量自动抓取定时器已启动")
|
||||
print("⏰ 执行时间:每小时整点执行抖音播放量抓取")
|
||||
print("⏹️ 按 Ctrl+C 停止定时器")
|
||||
|
||||
logging.info("🚀 抖音播放量自动抓取定时器已启动")
|
||||
logging.info(f"⏰ 执行时间:每小时整点执行抖音播放量抓取")
|
||||
logging.info("⏹️ 按 Ctrl+C 停止定时器")
|
||||
|
||||
# 启动时显示一次下次执行时间
|
||||
self.show_next_run()
|
||||
|
||||
try:
|
||||
while self.is_running:
|
||||
schedule.run_pending()
|
||||
time.sleep(1)
|
||||
|
||||
# 每1分钟显示一次状态
|
||||
current_time = int(time.time())
|
||||
if current_time - last_status_time >= 60: # 60秒 = 1分钟
|
||||
self.show_next_run()
|
||||
last_status_time = current_time
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n⏹️ 定时器已停止")
|
||||
logging.info("\n⏹️ 定时器已停止")
|
||||
self.is_running = False
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
try:
|
||||
parser = argparse.ArgumentParser(description='抖音播放量自动抓取定时器')
|
||||
parser.add_argument('--test', action='store_true', help='测试模式 - 立即执行一次')
|
||||
parser.add_argument('--once', action='store_true', help='立即执行一次并退出')
|
||||
parser.add_argument('--ranking-only', action='store_true', help='仅生成榜单(不抓取数据)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 设置日志配置 - 只在定时器模式下启用静默模式
|
||||
quiet_mode = not (args.test or args.once or args.ranking_only)
|
||||
setup_logging(quiet_mode=quiet_mode)
|
||||
|
||||
print("正在初始化定时器...")
|
||||
scheduler = DouyinAutoScheduler()
|
||||
|
||||
if args.test:
|
||||
scheduler._is_timer_mode = False
|
||||
print("执行测试模式...")
|
||||
scheduler.run_test()
|
||||
elif args.once:
|
||||
scheduler._is_timer_mode = False
|
||||
print("执行单次模式...")
|
||||
scheduler.run_once()
|
||||
elif args.ranking_only:
|
||||
scheduler._is_timer_mode = False
|
||||
print("执行榜单生成模式...")
|
||||
scheduler.run_ranking_only()
|
||||
else:
|
||||
scheduler._is_timer_mode = True
|
||||
print("启动定时器模式...")
|
||||
|
||||
# 显示定时器信息(使用print确保能看到)
|
||||
from datetime import datetime
|
||||
current_time = datetime.now()
|
||||
print(f"🕐 当前时间:{current_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print(f"⏰ 执行规则:每小时整点执行抖音播放量抓取")
|
||||
|
||||
# 计算下次执行时间
|
||||
next_hour = current_time.replace(minute=0, second=0, microsecond=0)
|
||||
if current_time.minute > 0 or current_time.second > 0:
|
||||
next_hour = next_hour.replace(hour=next_hour.hour + 1)
|
||||
if next_hour.hour >= 24:
|
||||
from datetime import timedelta
|
||||
next_hour = next_hour.replace(hour=0) + timedelta(days=1)
|
||||
|
||||
wait_seconds = (next_hour - current_time).total_seconds()
|
||||
wait_minutes = int(wait_seconds // 60)
|
||||
|
||||
print(f"⏰ 下次执行时间:{next_hour.strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print(f"⏳ 距离下次执行:{wait_minutes} 分钟 ({int(wait_seconds)} 秒)")
|
||||
print("💡 定时器正在等待中,将在整点自动执行任务...")
|
||||
print("⏹️ 按 Ctrl+C 停止定时器")
|
||||
|
||||
scheduler.setup_schedule()
|
||||
scheduler.start_scheduler()
|
||||
|
||||
print("程序执行完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"程序执行出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -1,52 +0,0 @@
|
||||
from flask import Flask, jsonify, send_from_directory
|
||||
from flask_cors import CORS
|
||||
import logging
|
||||
import os
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# 配置静态文件目录为dist
|
||||
# 说明:这里指向后端目录中的 dist(前端构建产物应复制或输出到此)
|
||||
dist_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'dist'))
|
||||
app.static_folder = dist_dir
|
||||
|
||||
# 为 SPA 提供静态文件与回退到 index.html 的路由
|
||||
@app.route('/')
|
||||
def serve_index():
|
||||
# 返回构建后的前端入口文件
|
||||
return send_from_directory(app.static_folder, 'index.html')
|
||||
|
||||
@app.route('/<path:path>')
|
||||
def serve_static_or_fallback(path):
|
||||
# 如果请求的文件存在则直接返回,否则回退到 index.html(用于前端路由)
|
||||
file_path = os.path.join(app.static_folder, path)
|
||||
if os.path.isfile(file_path):
|
||||
return send_from_directory(app.static_folder, path)
|
||||
return send_from_directory(app.static_folder, 'index.html')
|
||||
|
||||
CORS(app) # 允许跨域访问
|
||||
|
||||
# 配置日志
|
||||
# 确保logs目录存在
|
||||
logs_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'handlers', 'Rankings', 'logs')
|
||||
os.makedirs(logs_dir, exist_ok=True)
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler(os.path.join(logs_dir, 'app.log'), encoding='utf-8'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
|
||||
# 导入并注册蓝图
|
||||
from routers.rank_api_routes import rank_bp
|
||||
app.register_blueprint(rank_bp)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("启动主程序服务...")
|
||||
print("服务地址: http://localhost:8443")
|
||||
|
||||
app.run(host='0.0.0.0', port=8443, debug=True)
|
||||
@ -1,76 +0,0 @@
|
||||
import os
|
||||
import importlib
|
||||
|
||||
# 数据库配置
|
||||
MONGO_URI = "mongodb://localhost:27017"
|
||||
MONGO_DB_NAME = "Rankings"
|
||||
# MONGO_URI = "mongodb://mongouser:Jdei2243afN@172.16.0.6:27017,172.16.0.4:27017/test?replicaSet=cmgo-r6qkaern_0&authSource=admin"
|
||||
# MONGO_DB_NAME = "kemeng_media"
|
||||
|
||||
# 应用配置
|
||||
APP_ENV = os.getenv('APP_ENV', 'development')
|
||||
DEBUG = APP_ENV == 'development'
|
||||
|
||||
# 日志配置
|
||||
LOG_LEVEL = 'INFO'
|
||||
LOG_DIR = 'logs'
|
||||
|
||||
# 定时器配置
|
||||
SCHEDULER_TIME = "24:00" # 定时器执行时间,格式为 HH:MM (24小时制)
|
||||
|
||||
# 定时器环境变量配置
|
||||
TIMER_ENV_CONFIG = {
|
||||
'TIMER_MODE': '1', # 启用定时器模式,使数据保存到 Ranking_storage_list 集合
|
||||
'AUTO_CONTINUE': '1' # 启用自动模式,跳过详细数据获取以提高性能
|
||||
}
|
||||
|
||||
# 自动模式跳过函数配置
|
||||
AUTO_CONTINUE_SKIP_FUNCTIONS = [
|
||||
'get_collection_video_details', # 跳过合集视频详细数据获取
|
||||
'scroll_comments', # 跳过评论滚动
|
||||
# 可以在这里添加更多需要跳过的函数名
|
||||
]
|
||||
|
||||
# TOS/火山云对象存储配置
|
||||
TOS_CONFIG = {
|
||||
'access_key_id': os.getenv('TOS_ACCESS_KEY_ID', 'AKLTYjQyYmE1ZDAwZTY5NGZiOWI3ODZkZDhhOWE4MzVjODE'),
|
||||
'access_key_secret': os.getenv('TOS_ACCESS_KEY_SECRET', 'WlRKa05EbGhZVEUyTXpjNU5ESmpPRGt5T0RJNFl6QmhPR0pqTVRjMVpUWQ=='),
|
||||
'endpoint': 'https://tos-cn-beijing.volces.com',
|
||||
'region': 'cn-beijing',
|
||||
'bucket_name': os.getenv('TOS_BUCKET_NAME', 'km1'),
|
||||
'self_domain': os.getenv('TOS_SELF_DOMAIN', 'oss.xintiao85.com'),
|
||||
'disable_ssl_warnings': True
|
||||
}
|
||||
|
||||
# API配置(兼容现有代码)
|
||||
API_CONFIG = {
|
||||
'huoshan': {
|
||||
'AccessKey': TOS_CONFIG['access_key_id'],
|
||||
'SecretKey': TOS_CONFIG['access_key_secret']
|
||||
},
|
||||
'OSS_BUCKET_NAME': TOS_CONFIG['bucket_name'],
|
||||
'OSS_HOST': TOS_CONFIG['self_domain']
|
||||
}
|
||||
|
||||
# DeepSeek API 配置(用于评论总结功能)
|
||||
DEEPSEEK_CONFIG = {
|
||||
'api_key': 'sk-7b47e34bdcb549e6b00115a99b9b5c4c', # DeepSeek API密钥
|
||||
'api_base': 'https://api.deepseek.com/v1', # API基础URL
|
||||
'model': 'deepseek-chat', # 使用的模型
|
||||
'max_retries': 3, # 最大重试次数
|
||||
'retry_delays': [2, 5, 10], # 重试延迟(秒)
|
||||
'batch_size': 800, # 每批评论数量
|
||||
'max_tokens': 15000, # 每批最大token数
|
||||
'summary_max_length': 200 # 最终总结最大字数
|
||||
}
|
||||
|
||||
def apply_timer_environment():
|
||||
"""应用定时器环境变量配置"""
|
||||
for key, value in TIMER_ENV_CONFIG.items():
|
||||
os.environ[key] = value
|
||||
|
||||
def get_skip_functions():
|
||||
"""获取自动模式下需要跳过的函数列表"""
|
||||
return AUTO_CONTINUE_SKIP_FUNCTIONS.copy()
|
||||
|
||||
print(f"Successfully loaded configuration for environment: {APP_ENV}")
|
||||
@ -1,43 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7389531100718107954",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7393209049208130851",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7398121035452763432",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7404808266888252698",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7410761253204905235",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7416596680776158515",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7426956763892665654",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7429208304389328180",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7429519648518966555",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 9,
|
||||
"last_update": "2025-10-22T09:55:41.380145",
|
||||
"mix_name": "觉醒"
|
||||
}
|
||||
@ -1,123 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7421044063190699303",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7425556502057897270",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7428086785537821963",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7438273827555134774",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7431047488955632907",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7434029345796066623",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7438157166382550313",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7445146231225077003",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7448086473322106152",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7451825341225585954",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7463783714389085476",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7461502376516586752",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7471109492738952483",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7473376886383512884",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7476035323416415542",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7477826100815514889",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7480121799733202186",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7482669047008611610",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7483737890845920549",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7486742756103130394",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7487446983901269274",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7488238211139996978",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7489006391865216265",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7493926858061352202",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7498645852182203700",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7502686428054244660",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7504263612656078116",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7509429118845340982",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7509743276300291340",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 29,
|
||||
"last_update": "2025-10-22T09:56:01.930652",
|
||||
"mix_name": "《田螺姑娘》系列短剧"
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7472763101620751626",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7472763403182886156",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7472763009627065619",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7472764867905195327",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7472763523450293516",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7472763290104450315",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7472762896833908031",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7472763068376780071",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7472763556648193334",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7472763183409777939",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7472763426813529380",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 11,
|
||||
"last_update": "2025-10-22T09:56:08.613429",
|
||||
"mix_name": "兴安岭诡事"
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7462779038784933158",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7469001162868444443",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7469998771519130889",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7470709002284862720",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7471924777410645283",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7472791705268325641",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 6,
|
||||
"last_update": "2025-11-06T17:43:54.929209",
|
||||
"mix_name": "《青蛇传》"
|
||||
}
|
||||
@ -1,123 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7438832661914717475",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7441055420505918760",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7441797752599711010",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7444025334355201314",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7444767906782514432",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7445511144686587170",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7448480282623216934",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7449224455496256777",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7449966534602018074",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7452190854900370715",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7452932931934342437",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7453673782092303666",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7454416566910864650",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7458870546697080101",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7459612621348572443",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7460354830322699571",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7461097063850249499",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7461840320993463590",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7462583778557349147",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7463322906186091813",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7467405224320322854",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7468147493620960549",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7468887846011653402",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7469630119456361779",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7471114379258318106",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7471856794772491547",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7472598861073157403",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7473341167099186441",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7474085833558052159",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 29,
|
||||
"last_update": "2025-10-22T09:55:53.324636",
|
||||
"mix_name": "我在地府开当铺"
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7472623062152695074",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7457112388907666727",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7457505550188743973",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7457874864729345318",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7458965679874542858",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7459348765586001178",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7459760717202345254",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7460119944940883227",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7460847335602490675",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7461235696414559514",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7461939199000825151",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7462718793974910271",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7463090318058081555",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7463442602558246207",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7464950323786861874",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7466027689028193574",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7467918209505676585",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7469002478999096610",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7471609858727447808",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7472348174464650536",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 20,
|
||||
"last_update": "2025-10-22T09:56:14.396524",
|
||||
"mix_name": "《鲛人珠》"
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7445594903796518201",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7446432501297384763",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7454551962659376443",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7461234093703875898",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7466059018256026940",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7471994773054819641",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7474591461695147324",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7477189231987772729",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7478673086884187449",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7482757832807386426",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 10,
|
||||
"last_update": "2025-10-22T09:55:40.776573",
|
||||
"mix_name": "中式百妖集·白骨夫人"
|
||||
}
|
||||
@ -1,79 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7484932173439536421",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7485664110688898343",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7486419411969068314",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7487121293100305718",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7487526141658467620",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7488271435824123173",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7488650316636917044",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7489021106041392418",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7489378816629001510",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7489762229294730530",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7490506362162580771",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7491612455748128063",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7492356791385214249",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7493098513320938764",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7493470039182445843",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7494213861059579170",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7494607366281809179",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7496444061780299017",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 18,
|
||||
"last_update": "2025-10-22T09:56:10.891927",
|
||||
"mix_name": "忘川引"
|
||||
}
|
||||
@ -1,163 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7508787409220308287",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7510681348156214582",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7511991741650701607",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7513617213908159783",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7514504440183885067",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7515713983450451211",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7516611820073930020",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7517661121336184127",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7518746065160424743",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7519749002439429417",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7520472849899932964",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7521204206330547497",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7524573752886054196",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7530578815811128603",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7531758004685901065",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7532765269714816306",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535386089935129871",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7537427761216998708",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538524602385878324",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540051891523177737",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7541107806611475752",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7541853195853712640",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542682704836332835",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7543822135823830287",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7544261185663798555",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7544908029926313225",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7545999867550747914",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7548507331529657626",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7550700558206635274",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7551695429147086126",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7552568123484900646",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7552961927568313609",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553727704906943771",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555956738030177562",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7557016037955325194",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7558378239337467174",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7567050545257516331",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7568152326477942022",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7569217928420183332",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 39,
|
||||
"last_update": "2025-11-06T11:06:44.598400",
|
||||
"mix_name": "末世系列"
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7488147760089287973",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7488147616237096219",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7488146990686014757",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7488146612695485706",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7488145907737775369",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7488510699275029810",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7488511105959071013",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7488511733053607177",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7488885747047468315",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7488885905084714249",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 10,
|
||||
"last_update": "2025-10-22T09:56:06.856199",
|
||||
"mix_name": "全国首部奇幻探秘Al微短剧"
|
||||
}
|
||||
@ -1,143 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7486805231804681530",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7487578743611985210",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7488320388464774460",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7489343145566555449",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7490919031881469241",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7491659592909327675",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7492774825606483258",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7495283384688594233",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7496485765267754300",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7497545408492014905",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7499453865651014971",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7500569260890099004",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7502050015763189050",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7502796678514642233",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7505018729597963577",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7505763063816670521",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7507631537329458490",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7508730844816280890",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7513187407958150460",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7516524242646125884",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7518011787930520889",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7520976901600447801",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7537305227914546490",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546210488913595705",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546922516716539196",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7547625203083185468",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7549924545768295739",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553215200825134394",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554737168394374459",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555479694759284027",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556555317988724026",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7562121519012285755",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7563238756892757307",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7564756828878753061",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 34,
|
||||
"last_update": "2025-10-26T14:48:56.017802",
|
||||
"mix_name": "【中式百妖集·阴医】"
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7498696558876331318",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7498696627905940747",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7498696682746514751",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7498696464852471051",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7498991164272479551",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7499073383687114024",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7499073497541528867",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7499073601295944975",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7499073974123498804",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7499074026569059599",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7499074347429006626",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7499074195519802658",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7501976370621287718",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7502295085464227082",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7502419858789911846",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 15,
|
||||
"last_update": "2025-10-22T09:55:36.327209",
|
||||
"mix_name": "盲盒千岁"
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7504244447165615360",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7504244610508524834",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7504244482838138147",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7504244233549761844",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7504244628758007040",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7504244597929889076",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7504244625457024296",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7504244460637654307",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7504244631295528227",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7504244436004654371",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 10,
|
||||
"last_update": "2025-10-22T09:55:42.069016",
|
||||
"mix_name": "白骨精传奇"
|
||||
}
|
||||
@ -1,155 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7506067244128963892",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506450698897296692",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506711648292375846",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7508950346337815817",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7509774388443499803",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7510136494552550697",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7511916836351511862",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7514267874987363618",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7514880725972225319",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7516734442199682339",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7517445470608690447",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7517854243118730536",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7519774016958172443",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7520504060600929571",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7521579659352395062",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7522327810527055143",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7524266961459481891",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7524619540563512616",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7525332357025402151",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7527197348515171584",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7527580712619363618",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7527875102319316258",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7529722113029147944",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7529741695441833268",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7530556459571039507",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7532003677117091107",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7533520661294239012",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7534591926373190912",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535986779401325843",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538632635627703606",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538634729889877290",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121743873035555",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540870569168784655",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542402995653283112",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546532809460976948",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7547533101929467151",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7547910476743609652",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 37,
|
||||
"last_update": "2025-10-22T09:56:06.222702",
|
||||
"mix_name": "九尾狐男妖爱上我"
|
||||
}
|
||||
@ -1,235 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7506725945898847515",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726188392402186",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726062122929417",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506725983026777354",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726108860124443",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726665033223433",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726346563800370",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726016790940965",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726671379221787",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726090564586761",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726187318725898",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726296039230730",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726309150592294",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726284358094107",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726237205777714",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726182524685605",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726228850674954",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726166506605862",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726208801901851",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726298337758501",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726295435218213",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726377392016650",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726273427754278",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726355774524682",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726266536594726",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726314951462181",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726196588203290",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726170638028070",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726491531660594",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726608372239643",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726485236141362",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726498439564553",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726478663486758",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726546317659419",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726541062130982",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726526709157147",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726570220883251",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726627510897958",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726589883747622",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726632447593765",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726607525039369",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726659584822565",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726454617525541",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726418294869285",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726571374333210",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726517443956019",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726555305905418",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726472007109914",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726648675323173",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726475693919515",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726506924739850",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726523471220019",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726476633476379",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726532715433226",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726554542640394",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726533835410698",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506726608326118683",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 57,
|
||||
"last_update": "2025-10-22T09:55:59.359473",
|
||||
"mix_name": "如意坊"
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7549025803313384746",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7549025817850826003",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7549025811861409043",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7549025819251756342",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7549025818131893547",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7549025828185705767",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7549025818874252563",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7549025854819405097",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7549025851971521828",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7549025854999891239",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7549025873840590134",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7549025884955577643",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 12,
|
||||
"last_update": "2025-10-22T09:55:37.752763",
|
||||
"mix_name": "新平妖传"
|
||||
}
|
||||
@ -1,83 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7517472781332696332",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7517971761476979980",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7518395078306483510",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7518867709761686803",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7519654070961949988",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7520748725438811446",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7521798836566641983",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7529928581993041215",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7530321831966805284",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7531079565049941289",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7532255656586399017",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7533366600439762195",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7534435304296516918",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535534271813979431",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7536713189078175016",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538565796382002467",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538868180136037647",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540042632618167592",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7543715550619241743",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 19,
|
||||
"last_update": "2025-10-22T09:55:49.993319",
|
||||
"mix_name": "苏祥云"
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7527336931760295168",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7529100313467325711",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7539690162612079872",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7565426543275609378",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 4,
|
||||
"last_update": "2025-10-27T10:05:06.655628",
|
||||
"mix_name": "《小宝穿越|课本古诗文》"
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7476841679304051968",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7519893460598541609",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7548075831491415359",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 3,
|
||||
"last_update": "2025-10-22T09:56:11.433105",
|
||||
"mix_name": "僵尸师姐"
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7529542793484815642",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7529837310423190825",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7530392006963498279",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7531919686112529716",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7533566057529740559",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7536843946534669631",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538371048060898600",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540606531264433418",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542079999621319986",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7543609660469824822",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546200368829484339",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7548447317729234239",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7568747381357808923",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7568800392985791784",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 14,
|
||||
"last_update": "2025-11-06T17:48:06.014161",
|
||||
"mix_name": "青云修仙传"
|
||||
}
|
||||
@ -1,195 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7533908493456182538",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7534294543475150118",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7534686345395014950",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535429627486031144",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535787648519097634",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7536148527018167604",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7536756145902374159",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7536916803734359296",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7537291155520621858",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7537711561570290998",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538026362255281447",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538291293873556778",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538394363068452115",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538755908755590463",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7539159291803045159",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7539494201172512063",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7539497676841241898",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7539878435678653750",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7541743859181636910",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542114403907308810",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542826819817377070",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542829529639734574",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7543242228785876275",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7543611905496059174",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7543976834791165210",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7544364656991784219",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7545110659919383859",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7545458257129655567",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7545864375643409704",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546225454894599464",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546575160715889966",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546934519836806409",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7547336647730335027",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7547711167158897958",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7548073308894317875",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7548371214998244658",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7548819838773349670",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7549193055358733606",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7549574668735270190",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7549952043016883502",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7550323254922202378",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7550694134588263720",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7551058050275167540",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7551425021642607906",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7551790578816552226",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555439509588938024",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555845189366074662",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 47,
|
||||
"last_update": "2025-10-22T09:55:27.641577",
|
||||
"mix_name": "《风水之王》"
|
||||
}
|
||||
@ -1,279 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7535701272712777002",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701290232401194",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701286658821430",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701275858570532",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701262310985003",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701270364048682",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701309303901481",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701337573461289",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701337984519460",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701328048311571",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701353595800874",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701350357699903",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701363007769898",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701370368691492",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701350932335911",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701373900377385",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701392267283775",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701391872888106",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701400672619795",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701415142935827",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701423242120484",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701444511403305",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701443542535465",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701456305786153",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701442590428455",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701468439973162",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701494906031399",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701504049548580",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701470788750635",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701485867322687",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701502833200403",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701525734231359",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701525520239914",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701540334587190",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701524362693930",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701557594066217",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701561180163367",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701557178879268",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701577760263463",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701575201738026",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701602775108905",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701587893833014",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701608986873127",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701629786508580",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701644424531239",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701627827752211",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701660933492009",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701645422873897",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701646941179179",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701666885045540",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701674799779135",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701687755869476",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701681867214099",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701718235942180",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701716738559295",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701714779884841",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701730080656679",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701718118567211",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701734463720743",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701751924542756",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701727887084842",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701766235622710",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701764960521508",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701790038199606",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701815388540223",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701795494989098",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535701809969532214",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7543884485494885666",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 68,
|
||||
"last_update": "2025-10-22T09:56:42.812592",
|
||||
"mix_name": "奶团太后宫心计"
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7538319264500010274",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538319253661895976",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538319258040667426",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538319250730142991",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538319251308956928",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538319255717088546",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538319260087635200",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538319313824943360",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538319304874282280",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538319315666357519",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538319302512921890",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538319312289926400",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 12,
|
||||
"last_update": "2025-10-22T09:56:12.150347",
|
||||
"mix_name": "燕赤伏妖"
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7540121867537894694",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121873649093926",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121890577272115",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121898751986990",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121893479664934",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121902363233545",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121896306707721",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121918884547878",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121908944162099",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121914937822510",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121923020246310",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121952229330186",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121960240434470",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121958432689458",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121974115241267",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121980037582126",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121986127727922",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121993606171931",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121997993397550",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540121998798671150",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 20,
|
||||
"last_update": "2025-10-22T09:55:40.062057",
|
||||
"mix_name": "废柴仙尊在凡间逆袭"
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7520060104645823780",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7526440964852911360",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7531224944424897807",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7536966604467342634",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7540495853090245898",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546582568850754825",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7550653403244875044",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553221689224711466",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555476643674869028",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7557563705563581723",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7561372042944105770",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7563638353325821203",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7564982296051338534",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7565346285362548019",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 14,
|
||||
"last_update": "2025-10-27T11:04:23.469116",
|
||||
"mix_name": "暗黑神话《葫芦兄弟》大电影"
|
||||
}
|
||||
@ -1,335 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7542848851212848399",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848809219509539",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848845315607848",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848836981640482",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848861065301248",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848884834323746",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848870422711587",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848880208022824",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848850625662223",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848915637357858",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848899275443508",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848861832793344",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848917688405248",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848921295539490",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848955193822498",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848980061785359",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848967848004898",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848965998415104",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848979311070504",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848967923584308",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848984814062848",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542848924772470050",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849015503768847",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849044486343970",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849026937408820",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849016988536064",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849050052152576",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849050303827234",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849070256098560",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849073359883554",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849080590978339",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849054955277620",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849095237422376",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849079081012495",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849104309751092",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849117307915554",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849134680739106",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849125507779892",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849118029286691",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849137717349684",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849160806927650",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849105559604520",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849158164565263",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849186220297487",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849155958394146",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849176644717839",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849167325039924",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849189500226850",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849227328621839",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849207896395008",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849211025444111",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849162627386639",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849207703555328",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849259792502031",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849258521644323",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849225709735183",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849302184365364",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849280399232271",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849289127464232",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849295699987747",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849251013954850",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849316457565475",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849309453077794",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849330223287604",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849366608973071",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849356844649743",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849376066997544",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849360560737536",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849368232086819",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849392085110050",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849341845720335",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849423311637795",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849409881558287",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849389480529167",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849421172657442",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849419784359168",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849444547415348",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849467016383778",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849454877986048",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849456861973760",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7542849403191594255",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7543842316474207529",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 82,
|
||||
"last_update": "2025-10-22T09:56:26.875722",
|
||||
"mix_name": "200斤王妃天天想和离Q版"
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7546071652224765234",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546071610189548827",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546071665881500954",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546071681165495561",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546071674475515145",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546071695342243123",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546071699012193587",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546071697456254234",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546071708185283878",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546071712543165723",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546071720415857947",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546071728376565043",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546071716959685939",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 13,
|
||||
"last_update": "2025-10-22T09:55:35.570013",
|
||||
"mix_name": "末日来袭"
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7545715527935610150",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7546851498567716123",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7548440120316218662",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7549782003667782954",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7551038580261391616",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7552606420504464650",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555059788590206234",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 7,
|
||||
"last_update": "2025-10-22T09:56:07.724877",
|
||||
"mix_name": "《南城诡事》"
|
||||
}
|
||||
@ -1,283 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7553983620604579118",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983636282953011",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983647192288563",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983646374448411",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983671213133107",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983656532937994",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983683934326067",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983686446697737",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983694885752110",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983711918755122",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983719787285770",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983712300403978",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983719380487450",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983734438038830",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983745687178546",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983751466831150",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983762921524530",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983765584923914",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983782336957723",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983772199275785",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983806231891209",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983819997547803",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983816046595366",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983829485112627",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983842286161178",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983841656950042",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983832865737993",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983844874013978",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983845054352678",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983863744187699",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983873361677618",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983883587423514",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983889304194330",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983897294359818",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983891145575707",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983921776512266",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983915900374299",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983931104693542",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983944425737523",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983937924680998",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983956136381705",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983954274077961",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983968228494602",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983980819713326",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983971994995995",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983994157632814",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553983997076966706",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984007311019315",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984019042422026",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984009651408137",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984031956798770",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984033814859054",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984062147431690",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984067348352266",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984082489658675",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984074881256714",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984094493773094",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984092258241830",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984101271850267",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984113179462950",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984119844228379",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984134859722035",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984126999711022",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984154476514587",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984147111415049",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984154166168870",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984160797363502",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984182108572955",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7553984168468811046",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 69,
|
||||
"last_update": "2025-10-22T09:56:34.803954",
|
||||
"mix_name": "我在荒年当女帝"
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7554009575922355482",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554009571266809139",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554009591776840970",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554009593425218866",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554009609409662217",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554312903608913195",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554312915164187940",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554312925851176234",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554312927336041791",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554312932851617078",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555847841084706111",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555847841931955510",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7559429054969957684",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 13,
|
||||
"last_update": "2025-10-22T09:56:17.655969",
|
||||
"mix_name": "传武"
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7554750058999188776",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554750089743355151",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554750101374143784",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554750092658429199",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554750091874045224",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554750074610339107",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554750093157600512",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554750110882729251",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554751305978268963",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554750135679356195",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554750126988872995",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554750136304422144",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554750151248694568",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554750138007276835",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554750160660679970",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554750166654356770",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554750176326454562",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554750194152197416",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554750204159773967",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554750213030776064",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 20,
|
||||
"last_update": "2025-10-22T09:55:34.882918",
|
||||
"mix_name": "我的治愈系游戏"
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7555012459468295464",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555402801766092086",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555406069112130856",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7559152702782049576",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 4,
|
||||
"last_update": "2025-10-22T09:55:32.571577",
|
||||
"mix_name": "我在山海世界当神仙奶奶"
|
||||
}
|
||||
@ -1,215 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7555848626619043087",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848620394745103",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848626723884288",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848639554374947",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848651201908020",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848646504303872",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848634936479016",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848658214833443",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848701277670656",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848676313206050",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848687017151759",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848690846551330",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848704461196544",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848729824152832",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848716045847842",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848730759531828",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848731841662242",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848757745618228",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848764313947426",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848763940572456",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848769703644468",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848788842171663",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848805493558562",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848796584938787",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848822841232655",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848807460752675",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848826909625634",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848831548525859",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848844282449204",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848849298820367",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848861130951970",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848854726348072",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848849340910882",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848876754849064",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848903296371968",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848872417971471",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848898049363234",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848895910235426",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848919880699136",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848946078207267",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848941703613711",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848946078305571",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848977900375336",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848968379419939",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848963484683572",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7559890467404434724",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848989376089359",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848987807386880",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555848993603898664",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555849016483777827",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555849024037801216",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555849027057667368",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 52,
|
||||
"last_update": "2025-10-22T09:55:47.709471",
|
||||
"mix_name": "婉心计"
|
||||
}
|
||||
@ -1,251 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7556193043586600201",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193049286675762",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193069050105139",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193070857899302",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193075085741350",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193078072134962",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193079070379310",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193078973975818",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193108963118382",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193112775822638",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193121483115802",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193118438067494",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193118383623462",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193123634908462",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193146061868338",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193153837960458",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193142836448521",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193162352512282",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193174033616138",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193192723402034",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193187744845107",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193208854646025",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193223312510254",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193225766112550",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193223954287922",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193234033069338",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193250365771054",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193252056059182",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193259912006963",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193260188847369",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193250890075418",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193288026361114",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193288236190985",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193301905345802",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193302542961970",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193321299823881",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193328434269450",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193321668922633",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193341751266569",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193355420405019",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193355697458482",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193360436841737",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193361883974921",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193358012534025",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193378002668846",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193386659728691",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193404892253478",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193406922312970",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193422680329498",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193423468842266",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193432524410158",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193450471804187",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193451281222939",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193461007928586",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193466187861294",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193487763328265",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193490787421467",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193478774918410",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193479311887625",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193503307484425",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7556193515902995721",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 61,
|
||||
"last_update": "2025-10-22T09:56:50.400526",
|
||||
"mix_name": "我靠唱歌打脸全团"
|
||||
}
|
||||
@ -1,123 +0,0 @@
|
||||
{
|
||||
"episodes": [
|
||||
{
|
||||
"video_id": "7497105467341147428",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7501492304370601250",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7502772081621273894",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7503839169832062259",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7504986016153062656",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506112308838403343",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7506782177625394432",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7507204856379378959",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7507541015005744424",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7507946206644210959",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7508945450654141731",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7514129646461078799",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7519344535044066560",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7521994647779970338",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7524264312450698536",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7527207390493723938",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7531374405576248591",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7532778856979451136",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7535583078481136911",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7536884524291263759",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7538866617654365480",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7539751142214438144",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7549904246364376335",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7554992404740214016",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7555715283915312384",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7558050978226982179",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7560551213957500195",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7562056353343966464",
|
||||
"episode_num": 0
|
||||
},
|
||||
{
|
||||
"video_id": "7567981488823318927",
|
||||
"episode_num": 0
|
||||
}
|
||||
],
|
||||
"total_count": 29,
|
||||
"last_update": "2025-11-06T17:15:32.747557",
|
||||
"mix_name": "绝境逆袭"
|
||||
}
|
||||
@ -1,562 +0,0 @@
|
||||
from typing import Any, Optional
|
||||
import mimetypes
|
||||
from io import StringIO
|
||||
import os
|
||||
import tos
|
||||
import urllib3
|
||||
from urllib3.exceptions import InsecureRequestWarning
|
||||
from config import API_CONFIG
|
||||
# 火山对象存储
|
||||
class TOSClient:
|
||||
def __init__(
|
||||
self,
|
||||
access_key_id: str,
|
||||
access_key_secret: str,
|
||||
endpoint: str,
|
||||
region: str,
|
||||
bucket_name: str,
|
||||
self_domain: str,
|
||||
disable_ssl_warnings: bool = True
|
||||
):
|
||||
"""
|
||||
初始化OSS客户端
|
||||
|
||||
Args:
|
||||
access_key_id: ak
|
||||
access_key_secret: sk
|
||||
endpoint: OSS访问端点 (如: https://oss-cn-hangzhou.aliyuncs.com)
|
||||
bucket_name: 存储桶名称
|
||||
self_domain: 自定义域名
|
||||
disable_ssl_warnings: 是否禁用SSL警告
|
||||
"""
|
||||
# 禁用SSL警告(如果需要)
|
||||
if disable_ssl_warnings:
|
||||
urllib3.disable_warnings(InsecureRequestWarning)
|
||||
sts_token: str = "token_test"
|
||||
self.bucket_name = bucket_name
|
||||
self.self_domain = self_domain
|
||||
self.endpoint = endpoint
|
||||
self.client = tos.TosClientV2(
|
||||
ak=access_key_id,
|
||||
sk=access_key_secret,
|
||||
endpoint=self_domain,
|
||||
region=region,
|
||||
is_custom_domain=True,
|
||||
# bucket_name,
|
||||
# security_token=sts_token,
|
||||
connection_time=30, socket_timeout=60, max_retry_count=3
|
||||
)
|
||||
|
||||
def get_base_url(self, object_key: str) -> str:
|
||||
"""获取基础URL(不带签名参数)"""
|
||||
# endpoint = self.endpoint.replace('https://', '').replace('http://', '')
|
||||
return f"https://{self.self_domain}/{object_key}"
|
||||
|
||||
def generate_url(self, object_key: str, expires: int = 3600) -> str:
|
||||
"""生成带签名的临时访问URL"""
|
||||
# 生成签名URL
|
||||
pre_signed_url_output = self.client.pre_signed_url(
|
||||
tos.HttpMethodType.Http_Method_Get,
|
||||
bucket=self.bucket_name,
|
||||
key=object_key,
|
||||
expires=expires)
|
||||
return pre_signed_url_output.signed_url
|
||||
|
||||
def upload_string(
|
||||
self,
|
||||
content_str: str,
|
||||
object_key: str,
|
||||
headers: Optional[dict] = None,
|
||||
return_url: bool = True,
|
||||
) -> str:
|
||||
"""
|
||||
上传本地文件到OSS
|
||||
|
||||
Args:
|
||||
local_file_path: 本地文件路径
|
||||
object_key: OSS对象键(路径),如果为None则使用本地文件名
|
||||
headers: 自定义HTTP头
|
||||
|
||||
Returns:
|
||||
str: 文件在OSS的公开URL
|
||||
|
||||
Raises:
|
||||
Exception: 如果上传失败
|
||||
"""
|
||||
|
||||
|
||||
try:
|
||||
# if headers is None:
|
||||
# headers = {}
|
||||
# if content_type and 'Content-Type' not in headers:
|
||||
# headers['Content-Type'] = content_type
|
||||
content = StringIO(content_str)
|
||||
result = self.client.put_object(
|
||||
bucket=self.bucket_name,
|
||||
key=object_key,
|
||||
content_type='text/plain',
|
||||
content=content,
|
||||
)
|
||||
|
||||
# HTTP状态码
|
||||
print('upload_string http status code:{}'.format(result.status_code))
|
||||
# 请求ID。请求ID是本次请求的唯一标识,建议在日志中添加此参数
|
||||
# print('request_id: {}'.format(result.request_id))
|
||||
# hash_crc64_ecma 表示该对象的64位CRC值, 可用于验证上传对象的完整性
|
||||
# print('crc64: {}'.format(result.hash_crc64_ecma))
|
||||
if result.status_code != 200:
|
||||
raise Exception(f"上传失败,HTTP状态码: {result.status_code}")
|
||||
|
||||
return self.get_base_url(object_key) if return_url else object_key # 修改返回逻辑
|
||||
except Exception as e:
|
||||
raise Exception(f"上传文件到OSS失败: {str(e)}")
|
||||
|
||||
|
||||
def upload_file(
|
||||
self,
|
||||
local_file_path: str,
|
||||
object_key: Optional[str] = None,
|
||||
headers: Optional[dict] = None,
|
||||
return_url: bool = True,
|
||||
expires: int = 3600 # 新增参数,默认1小时
|
||||
) -> str:
|
||||
"""
|
||||
上传本地文件到OSS
|
||||
|
||||
Args:
|
||||
local_file_path: 本地文件路径
|
||||
object_key: OSS对象键(路径),如果为None则使用本地文件名
|
||||
headers: 自定义HTTP头
|
||||
|
||||
Returns:
|
||||
str: 文件在OSS的公开URL
|
||||
|
||||
Raises:
|
||||
Exception: 如果上传失败
|
||||
"""
|
||||
if not os.path.exists(local_file_path):
|
||||
raise FileNotFoundError(f"本地文件不存在: {local_file_path}")
|
||||
|
||||
# 如果没有指定object_key,则使用文件名
|
||||
if object_key is None:
|
||||
object_key = os.path.basename(local_file_path)
|
||||
|
||||
# 自动设置Content-Type
|
||||
content_type, _ = mimetypes.guess_type(local_file_path)
|
||||
|
||||
try:
|
||||
# file_name为本地文件的完整路径。
|
||||
result = self.client.put_object_from_file(
|
||||
bucket=self.bucket_name,
|
||||
key=object_key,
|
||||
content_type=content_type or '',
|
||||
file_path=local_file_path,
|
||||
)
|
||||
|
||||
if result.status_code != 200:
|
||||
raise Exception(f"上传失败,HTTP状态码: {result.status_code}")
|
||||
|
||||
return self.get_base_url(object_key) if return_url else object_key # 修改返回逻辑
|
||||
except Exception as e:
|
||||
raise Exception(f"上传文件到OSS失败: {str(e)}")
|
||||
|
||||
|
||||
def upload_bytes(
|
||||
self,
|
||||
data: bytes,
|
||||
object_key: str,
|
||||
content_type: Optional[str] = None,
|
||||
headers: Optional[dict] = None,
|
||||
return_url: bool = True,
|
||||
expires: int = 3600 # 新增参数
|
||||
) -> str:
|
||||
"""
|
||||
上传字节数据到OSS
|
||||
Args:
|
||||
data: 要上传的字节数据
|
||||
object_key: OSS对象键(路径)
|
||||
content_type: 内容类型 (如: image/jpeg)
|
||||
headers: 自定义HTTP头
|
||||
Returns:
|
||||
str: 文件在OSS的公开URL
|
||||
Raises:
|
||||
Exception: 如果上传失败
|
||||
"""
|
||||
|
||||
try:
|
||||
result = self.client.put_object(
|
||||
bucket=self.bucket_name,
|
||||
key=object_key,
|
||||
content_type=content_type or 'application/octet-stream',
|
||||
content=data,
|
||||
)
|
||||
|
||||
if result.status_code != 200:
|
||||
raise Exception(f"上传失败,HTTP状态码: {result.status_code}")
|
||||
|
||||
return self.get_base_url(object_key) if return_url else object_key # 修改返回逻辑
|
||||
except Exception as e:
|
||||
raise Exception(f"上传字节数据到OSS失败: {str(e)}")
|
||||
|
||||
def upload_from_url(
|
||||
self,
|
||||
url: str,
|
||||
object_key: str,
|
||||
headers: Optional[dict] = None,
|
||||
timeout: int = 30,
|
||||
return_url: bool = True,
|
||||
expires: int = 3600 # 新增参数
|
||||
) -> str:
|
||||
"""
|
||||
从网络URL下载文件并上传到OSS
|
||||
|
||||
Args:
|
||||
url: 网络文件URL
|
||||
object_key: OSS对象键(路径)
|
||||
headers: 自定义HTTP头
|
||||
timeout: 下载超时时间(秒)
|
||||
return_url: 是否返回完整URL
|
||||
|
||||
Returns:
|
||||
str: 文件在OSS的公开URL或object_key
|
||||
|
||||
Raises:
|
||||
Exception: 如果下载或上传失败
|
||||
"""
|
||||
import requests
|
||||
from io import BytesIO
|
||||
|
||||
if not url.startswith(('http://', 'https://')):
|
||||
raise ValueError("URL必须以http://或https://开头")
|
||||
|
||||
try:
|
||||
# 下载文件
|
||||
response = requests.get(url, stream=True, timeout=timeout)
|
||||
response.raise_for_status()
|
||||
|
||||
# 获取内容类型
|
||||
content_type = response.headers.get('Content-Type', '')
|
||||
if not content_type:
|
||||
content_type = mimetypes.guess_type(url)[0] or 'application/octet-stream'
|
||||
|
||||
# 上传到OSS
|
||||
return self.upload_bytes(
|
||||
data=response.content,
|
||||
object_key=object_key,
|
||||
content_type=content_type,
|
||||
headers=headers,
|
||||
return_url=return_url,
|
||||
expires=expires # 传递参数
|
||||
)
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise Exception(f"下载网络文件失败: {str(e)}")
|
||||
except Exception as e:
|
||||
raise Exception(f"上传网络文件到OSS失败: {str(e)}")
|
||||
|
||||
def _format_object_key(self, object_key: str) -> str:
|
||||
"""
|
||||
格式化OSS对象键(路径)
|
||||
"""
|
||||
# 如果object_key包含self_domain,截取self_domain后面的字符作为新的object_key
|
||||
if self.self_domain and self.self_domain in object_key:
|
||||
# 找到self_domain在object_key中的位置,截取后面的部分
|
||||
domain_index = object_key.find(self.self_domain)
|
||||
if domain_index != -1:
|
||||
# 截取self_domain后面的部分,去掉开头的斜杠
|
||||
object_key = object_key[domain_index + len(self.self_domain):].lstrip('/')
|
||||
return object_key
|
||||
|
||||
# 删除文件
|
||||
def delete_file(self, object_key: str) -> bool:
|
||||
"""
|
||||
删除OSS上的文件
|
||||
|
||||
Args:
|
||||
object_key: OSS对象键(路径)
|
||||
|
||||
Returns:
|
||||
bool: 删除是否成功
|
||||
"""
|
||||
try:
|
||||
self.client.delete_object(
|
||||
bucket=self.bucket_name,
|
||||
key=self._format_object_key(object_key),
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"删除文件失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def download_file(self, object_key: str) -> bytes:
|
||||
"""
|
||||
从TOS下载文件并返回文件数据
|
||||
|
||||
Args:
|
||||
object_key: OSS对象键(路径)
|
||||
|
||||
Returns:
|
||||
bytes: 文件的字节数据
|
||||
|
||||
Raises:
|
||||
Exception: 如果下载失败
|
||||
"""
|
||||
try:
|
||||
object_key = self._format_object_key(object_key)
|
||||
|
||||
object_stream = self.client.get_object(
|
||||
bucket=self.bucket_name,
|
||||
key=object_key,
|
||||
)
|
||||
content = object_stream.read() or b''
|
||||
if not content:
|
||||
raise Exception(f"文件内容为空: {object_key}")
|
||||
return content
|
||||
except tos.exceptions.TosClientError as e:
|
||||
# 操作失败,捕获客户端异常,一般情况为非法请求参数或网络异常
|
||||
print('TOS下载 fail with client error, message:{}, cause: {}'.format(e.message, e.cause))
|
||||
raise Exception(f"下载异常: {object_key} {e.message}")
|
||||
except tos.exceptions.TosServerError as e:
|
||||
# 操作失败,捕获服务端异常,可从返回信息中获取详细错误信息
|
||||
print('TOS下载 fail with server error, code: {}'.format(e.code))
|
||||
# request id 可定位具体问题,强烈建议日志中保存
|
||||
print('TOS下载 error with request id: {}'.format(e.request_id))
|
||||
print('TOS下载 error with message: {}'.format(e.message))
|
||||
print('TOS下载 error with http code: {}'.format(e.status_code))
|
||||
print('TOS下载 error with ec: {}'.format(e.ec))
|
||||
print('TOS下载 error with request url: {}'.format(e.request_url))
|
||||
raise Exception(f"下载异常: {object_key} {e.message}")
|
||||
except Exception as e:
|
||||
raise Exception(f"下载文件失败: {str(e)}")
|
||||
|
||||
|
||||
class TOSChunkUploader:
|
||||
"""TOS分片上传类"""
|
||||
|
||||
def __init__(self, tos_client: TOSClient):
|
||||
"""
|
||||
初始化分片上传器
|
||||
|
||||
Args:
|
||||
tos_client: TOS客户端实例
|
||||
"""
|
||||
self.client = tos_client.client
|
||||
self.bucket_name = tos_client.bucket_name
|
||||
self.self_domain = tos_client.self_domain
|
||||
|
||||
def init_multipart_upload(self, object_key: str, content_type: Optional[str] = None) -> Optional[str]:
|
||||
"""
|
||||
初始化分片上传
|
||||
|
||||
Args:
|
||||
object_key: 对象键
|
||||
content_type: 内容类型
|
||||
|
||||
Returns:
|
||||
str: 上传ID
|
||||
|
||||
Raises:
|
||||
Exception: 如果初始化失败
|
||||
"""
|
||||
try:
|
||||
# 设置默认内容类型
|
||||
if not content_type:
|
||||
content_type = mimetypes.guess_type(object_key)[0] or 'application/octet-stream'
|
||||
|
||||
# 初始化分片上传
|
||||
result = self.client.create_multipart_upload(
|
||||
bucket=self.bucket_name,
|
||||
key=object_key,
|
||||
content_type=content_type
|
||||
)
|
||||
|
||||
return result.upload_id
|
||||
|
||||
except tos.exceptions.TosClientError as e:
|
||||
raise Exception(f"初始化分片上传失败(客户端错误): {e.message}")
|
||||
except tos.exceptions.TosServerError as e:
|
||||
raise Exception(f"初始化分片上传失败(服务端错误): {e.message}")
|
||||
except Exception as e:
|
||||
raise Exception(f"初始化分片上传失败: {str(e)}")
|
||||
|
||||
def upload_part(self, object_key: str, upload_id: str, part_number: int, data: bytes) -> dict:
|
||||
"""
|
||||
上传分片
|
||||
|
||||
Args:
|
||||
object_key: 对象键
|
||||
upload_id: 上传ID
|
||||
part_number: 分片号(从1开始)
|
||||
data: 分片数据
|
||||
|
||||
Returns:
|
||||
dict: 包含完整分片信息的字典
|
||||
|
||||
Raises:
|
||||
Exception: 如果上传失败
|
||||
"""
|
||||
try:
|
||||
from io import BytesIO
|
||||
import hashlib
|
||||
|
||||
# 计算分片大小
|
||||
part_size = len(data)
|
||||
|
||||
# 计算CRC64(如果需要的话,这里先设为None)
|
||||
hash_crc64_ecma = None
|
||||
|
||||
# 上传分片
|
||||
result = self.client.upload_part(
|
||||
bucket=self.bucket_name,
|
||||
key=object_key,
|
||||
upload_id=upload_id,
|
||||
part_number=part_number,
|
||||
content=BytesIO(data)
|
||||
)
|
||||
|
||||
return {
|
||||
'part_number': part_number,
|
||||
'etag': result.etag,
|
||||
'part_size': part_size,
|
||||
'hash_crc64_ecma': hash_crc64_ecma,
|
||||
'is_completed': True
|
||||
}
|
||||
|
||||
except tos.exceptions.TosClientError as e:
|
||||
raise Exception(f"上传分片失败(客户端错误): {e.message}")
|
||||
except tos.exceptions.TosServerError as e:
|
||||
raise Exception(f"上传分片失败(服务端错误): {e.message}")
|
||||
except Exception as e:
|
||||
raise Exception(f"上传分片失败: {str(e)}")
|
||||
|
||||
def complete_multipart_upload(self, object_key: str, upload_id: str, parts: list) -> str:
|
||||
"""
|
||||
完成分片上传
|
||||
|
||||
Args:
|
||||
object_key: 对象键
|
||||
upload_id: 上传ID
|
||||
parts: 分片信息列表,每个元素包含part_number和etag
|
||||
|
||||
Returns:
|
||||
str: 文件的完整URL
|
||||
|
||||
Raises:
|
||||
Exception: 如果完成上传失败
|
||||
"""
|
||||
try:
|
||||
# 按分片号排序
|
||||
sorted_parts = sorted(parts, key=lambda x: x['part_number'])
|
||||
|
||||
# 构建分片列表并计算偏移量
|
||||
part_list = []
|
||||
current_offset = 0
|
||||
|
||||
for part in sorted_parts:
|
||||
part_list.append(tos.models2.PartInfo(
|
||||
part_number=part['part_number'],
|
||||
etag=part['etag'],
|
||||
part_size=part.get('part_size'),
|
||||
offset=current_offset,
|
||||
hash_crc64_ecma=part.get('hash_crc64_ecma'),
|
||||
is_completed=part.get('is_completed', True)
|
||||
))
|
||||
|
||||
# 更新偏移量
|
||||
if part.get('part_size'):
|
||||
current_offset += part['part_size']
|
||||
|
||||
# 完成分片上传
|
||||
result = self.client.complete_multipart_upload(
|
||||
bucket=self.bucket_name,
|
||||
key=object_key,
|
||||
upload_id=upload_id,
|
||||
parts=part_list
|
||||
)
|
||||
|
||||
# 返回完整URL
|
||||
return f"https://{self.self_domain}/{object_key}"
|
||||
|
||||
except tos.exceptions.TosClientError as e:
|
||||
raise Exception(f"完成分片上传失败(客户端错误): {e.message}")
|
||||
except tos.exceptions.TosServerError as e:
|
||||
raise Exception(f"完成分片上传失败(服务端错误): {e.message}")
|
||||
except Exception as e:
|
||||
raise Exception(f"完成分片上传失败: {str(e)}")
|
||||
|
||||
def abort_multipart_upload(self, object_key: str, upload_id: str) -> bool:
|
||||
"""
|
||||
取消分片上传
|
||||
|
||||
Args:
|
||||
object_key: 对象键
|
||||
upload_id: 上传ID
|
||||
|
||||
Returns:
|
||||
bool: 是否取消成功
|
||||
"""
|
||||
try:
|
||||
self.client.abort_multipart_upload(
|
||||
bucket=self.bucket_name,
|
||||
key=object_key,
|
||||
upload_id=upload_id
|
||||
)
|
||||
return True
|
||||
|
||||
except tos.exceptions.TosClientError as e:
|
||||
print(f"取消分片上传失败(客户端错误): {e.message}")
|
||||
return False
|
||||
except tos.exceptions.TosServerError as e:
|
||||
print(f"取消分片上传失败(服务端错误): {e.message}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"取消分片上传失败: {str(e)}")
|
||||
return False
|
||||
|
||||
def list_parts(self, object_key: str, upload_id: str) -> list:
|
||||
"""
|
||||
列出已上传的分片
|
||||
|
||||
Args:
|
||||
object_key: 对象键
|
||||
upload_id: 上传ID
|
||||
|
||||
Returns:
|
||||
list: 已上传的分片列表
|
||||
"""
|
||||
try:
|
||||
result = self.client.list_parts(
|
||||
bucket=self.bucket_name,
|
||||
key=object_key,
|
||||
upload_id=upload_id
|
||||
)
|
||||
|
||||
parts = []
|
||||
for part in result.parts:
|
||||
parts.append({
|
||||
'part_number': part.part_number,
|
||||
'etag': part.etag,
|
||||
'size': part.size,
|
||||
'last_modified': part.last_modified
|
||||
})
|
||||
|
||||
return parts
|
||||
|
||||
except Exception as e:
|
||||
print(f"列出分片失败: {str(e)}")
|
||||
return []
|
||||
|
||||
|
||||
# 创建OSS客户端
|
||||
from config import TOS_CONFIG
|
||||
oss_client = TOSClient(
|
||||
access_key_id=TOS_CONFIG['access_key_id'],
|
||||
access_key_secret=TOS_CONFIG['access_key_secret'],
|
||||
endpoint=TOS_CONFIG['endpoint'],
|
||||
region=TOS_CONFIG['region'],
|
||||
bucket_name=TOS_CONFIG['bucket_name'],
|
||||
self_domain=TOS_CONFIG['self_domain'],
|
||||
disable_ssl_warnings=TOS_CONFIG['disable_ssl_warnings']
|
||||
)
|
||||
|
||||
# 创建分片上传器
|
||||
chunk_uploader = TOSChunkUploader(oss_client)
|
||||
16
config.py
Normal file
@ -0,0 +1,16 @@
|
||||
import os
|
||||
import importlib
|
||||
|
||||
# 数据库配置
|
||||
MONGO_URI = "mongodb://localhost:27017"
|
||||
MONGO_DB_NAME = "Rankings"
|
||||
|
||||
# 应用配置
|
||||
APP_ENV = os.getenv('APP_ENV', 'development')
|
||||
DEBUG = APP_ENV == 'development'
|
||||
|
||||
# 日志配置
|
||||
LOG_LEVEL = 'INFO'
|
||||
LOG_DIR = 'logs'
|
||||
|
||||
print(f"Successfully loaded configuration for environment: {APP_ENV}")
|
||||
960
docs/API接口文档.md
@ -1,960 +0,0 @@
|
||||
# 抖音播放量数据API接口文档
|
||||
|
||||
## 概述
|
||||
|
||||
本API服务提供抖音播放量数据和文章内容的查询、搜索、统计等功能,专为小程序优化设计。
|
||||
|
||||
**基础信息**
|
||||
- 服务地址:`http://localhost:5001`
|
||||
- 数据源:MongoDB数据库
|
||||
- 数据更新:每晚24:00自动更新
|
||||
- 响应格式:JSON
|
||||
|
||||
## 通用响应格式
|
||||
|
||||
所有API接口都遵循以下响应格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {},
|
||||
"message": "操作成功",
|
||||
"update_time": "2025-10-17 15:30:00"
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明**
|
||||
- `success`: 请求是否成功
|
||||
- `data`: 返回的数据内容
|
||||
- `message`: 操作结果描述
|
||||
- `update_time`: 数据更新时间
|
||||
|
||||
## 数据模型
|
||||
|
||||
### 合集数据项
|
||||
```json
|
||||
{
|
||||
"_id": "674f1234567890abcdef",
|
||||
"batch_time": "2025-10-17 15:30:00",
|
||||
"mix_name": "合集名称",
|
||||
"video_url": "https://www.douyin.com/video/xxx",
|
||||
"playcount": "1.2亿",
|
||||
"play_vv": 120000000,
|
||||
"request_id": "request_xxx",
|
||||
"rank": 1,
|
||||
"cover_image_url": "https://p3.douyinpic.com/xxx"
|
||||
}
|
||||
```
|
||||
|
||||
### 文章数据项
|
||||
```json
|
||||
{
|
||||
"_id": "68f3ad112ba085e5d676537e",
|
||||
"title": "文章标题",
|
||||
"author_id": "test_user_1",
|
||||
"cover_image": "封面图片URL",
|
||||
"status": "draft",
|
||||
"summary": "文章摘要",
|
||||
"created_at": "2025-10-18 15:06:57",
|
||||
"likes": [],
|
||||
"likes_count": 0
|
||||
}
|
||||
```
|
||||
|
||||
### 文章详情数据项
|
||||
```json
|
||||
{
|
||||
"_id": "68f3ad112ba085e5d676537e",
|
||||
"title": "文章标题",
|
||||
"content": "文章完整内容",
|
||||
"author_id": "test_user_1",
|
||||
"cover_image": "封面图片URL",
|
||||
"status": "draft",
|
||||
"summary": "文章摘要",
|
||||
"created_at": "2025-10-18 15:06:57",
|
||||
"likes": [],
|
||||
"likes_count": 0
|
||||
}
|
||||
```
|
||||
|
||||
### 分页信息
|
||||
```json
|
||||
{
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"limit": 20,
|
||||
"total": 100,
|
||||
"pages": 5,
|
||||
"has_next": true,
|
||||
"has_prev": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API接口列表
|
||||
|
||||
### 1. 首页信息
|
||||
|
||||
**接口地址**
|
||||
```
|
||||
GET /
|
||||
```
|
||||
|
||||
**功能描述**
|
||||
获取API服务的基本信息和所有可用接口列表
|
||||
|
||||
**请求参数**
|
||||
无
|
||||
|
||||
**响应示例**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"name": "抖音播放量数据API服务",
|
||||
"version": "2.0",
|
||||
"description": "主程序服务 - 整合小程序API功能",
|
||||
"endpoints": {
|
||||
"/api/rank/videos": "获取视频列表 (支持分页和排序)",
|
||||
"/api/rank/top": "获取热门视频榜单",
|
||||
"/api/rank/search": "搜索视频",
|
||||
"/api/rank/detail": "获取视频详情",
|
||||
"/api/rank/stats": "获取统计信息",
|
||||
"/api/rank/health": "健康检查",
|
||||
"/api/rank/rankings": "获取榜单列表",
|
||||
"/api/rank/rankings/dates": "获取可用榜单日期",
|
||||
"/api/rank/rankings/types": "获取榜单类型",
|
||||
"/api/rank/rankings/latest": "获取最新榜单",
|
||||
"/api/rank/rankings/stats": "获取榜单统计",
|
||||
"/api/article/list": "获取文章列表 (支持分页和排序)",
|
||||
"/api/article/search": "搜索文章",
|
||||
"/api/article/detail": "获取文章详情",
|
||||
"/api/article/stats": "获取文章统计信息",
|
||||
"/api/article/health": "文章服务健康检查"
|
||||
},
|
||||
"features": [
|
||||
"分页支持",
|
||||
"多种排序方式",
|
||||
"搜索功能",
|
||||
"详情查看",
|
||||
"统计分析",
|
||||
"榜单查询",
|
||||
"动态排序",
|
||||
"小程序优化",
|
||||
"文章管理",
|
||||
"内容搜索"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 获取视频列表
|
||||
|
||||
**接口地址**
|
||||
```
|
||||
GET /api/rank/videos
|
||||
```
|
||||
|
||||
**功能描述**
|
||||
获取分页的视频合集列表,支持多种排序方式
|
||||
|
||||
**请求参数**
|
||||
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|--------|------|------|--------|------|
|
||||
| page | int | 否 | 1 | 页码 |
|
||||
| limit | int | 否 | 20 | 每页数量 |
|
||||
| sort | string | 否 | playcount | 排序方式:playcount(播放量) / growth(增长量) |
|
||||
| start_date | string | 否 | 昨天 | 增长计算开始日期(格式: YYYY-MM-DD),仅在sort=growth时有效 |
|
||||
| end_date | string | 否 | 今天 | 增长计算结束日期(格式: YYYY-MM-DD),仅在sort=growth时有效 |
|
||||
|
||||
**数据源说明**
|
||||
- `sort=playcount`:从Rankings_list集合动态查询当日播放量数据
|
||||
- `sort=growth`:从Ranking_storage集合读取预计算的增长排名数据(由定时任务生成)
|
||||
|
||||
**使用示例**
|
||||
```
|
||||
# 按播放量排序
|
||||
GET /api/rank/videos?page=1&limit=20&sort=playcount
|
||||
|
||||
# 按增长量排序(默认昨天到今天的增长)
|
||||
GET /api/rank/videos?page=1&limit=20&sort=growth
|
||||
|
||||
# 按自定义日期范围的增长排序
|
||||
GET /api/rank/videos?page=1&limit=20&sort=growth&start_date=2025-10-16&end_date=2025-10-17
|
||||
```
|
||||
|
||||
**响应示例**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"_id": "674f1234567890abcdef",
|
||||
"batch_time": "2025-10-17 15:30:00",
|
||||
"mix_name": "热门合集1",
|
||||
"video_url": "https://www.douyin.com/video/xxx",
|
||||
"playcount": "1.2亿",
|
||||
"play_vv": 120000000,
|
||||
"request_id": "request_xxx",
|
||||
"rank": 1,
|
||||
"cover_image_url": "https://p3.douyinpic.com/xxx",
|
||||
"growth": 5000000,
|
||||
"growth_rate": 4.35
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"limit": 20,
|
||||
"total": 100,
|
||||
"pages": 5,
|
||||
"has_next": true,
|
||||
"has_prev": false
|
||||
},
|
||||
"sort_by": "growth",
|
||||
"data_source": "ranking_storage",
|
||||
"update_time": "2025-10-17 15:30:00"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 获取热门榜单
|
||||
|
||||
**接口地址**
|
||||
```
|
||||
GET /api/rank/top
|
||||
```
|
||||
|
||||
**功能描述**
|
||||
获取热门视频榜单(按播放量排序)
|
||||
|
||||
**请求参数**
|
||||
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|--------|------|------|--------|------|
|
||||
| limit | int | 否 | 10 | 返回数量 |
|
||||
|
||||
**使用示例**
|
||||
```
|
||||
GET /api/rank/top?limit=10
|
||||
```
|
||||
|
||||
**响应示例**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"_id": "674f1234567890abcdef",
|
||||
"batch_time": "2025-10-17 15:30:00",
|
||||
"mix_name": "热门合集1",
|
||||
"video_url": "https://www.douyin.com/video/xxx",
|
||||
"playcount": "1.2亿",
|
||||
"play_vv": 120000000,
|
||||
"request_id": "request_xxx",
|
||||
"rank": 1,
|
||||
"cover_image_url": "https://p3.douyinpic.com/xxx"
|
||||
}
|
||||
],
|
||||
"total": 10,
|
||||
"update_time": "2025-10-17 15:30:00"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 搜索视频
|
||||
|
||||
**接口地址**
|
||||
```
|
||||
GET /api/rank/search
|
||||
```
|
||||
|
||||
**功能描述**
|
||||
根据关键词搜索视频合集
|
||||
|
||||
**请求参数**
|
||||
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|--------|------|------|--------|------|
|
||||
| q | string | 是 | - | 搜索关键词 |
|
||||
| page | int | 否 | 1 | 页码 |
|
||||
| limit | int | 否 | 10 | 每页数量 |
|
||||
|
||||
**使用示例**
|
||||
```
|
||||
GET /api/rank/search?q=关键词&page=1&limit=10
|
||||
```
|
||||
|
||||
**响应示例**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"_id": "674f1234567890abcdef",
|
||||
"batch_time": "2025-10-17 15:30:00",
|
||||
"mix_name": "包含关键词的合集",
|
||||
"video_url": "https://www.douyin.com/video/xxx",
|
||||
"playcount": "1.2亿",
|
||||
"play_vv": 120000000,
|
||||
"request_id": "request_xxx",
|
||||
"rank": 1,
|
||||
"cover_image_url": "https://p3.douyinpic.com/xxx"
|
||||
}
|
||||
],
|
||||
"keyword": "关键词",
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"limit": 10,
|
||||
"total": 15,
|
||||
"pages": 2,
|
||||
"has_next": true,
|
||||
"has_prev": false
|
||||
},
|
||||
"update_time": "2025-10-17 15:30:00"
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 获取视频详情
|
||||
|
||||
**接口地址**
|
||||
```
|
||||
GET /api/rank/detail
|
||||
```
|
||||
|
||||
**功能描述**
|
||||
获取指定合集的详细信息
|
||||
|
||||
**请求参数**
|
||||
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|--------|------|------|--------|------|
|
||||
| id | string | 是 | - | 合集ID(支持ObjectId、合集名称、request_id) |
|
||||
|
||||
**使用示例**
|
||||
```
|
||||
GET /api/rank/detail?id=674f1234567890abcdef
|
||||
```
|
||||
|
||||
**响应示例**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"_id": "674f1234567890abcdef",
|
||||
"batch_time": "2025-10-17 15:30:00",
|
||||
"mix_name": "合集名称",
|
||||
"video_url": "https://www.douyin.com/video/xxx",
|
||||
"playcount": "1.2亿",
|
||||
"play_vv": 120000000,
|
||||
"request_id": "request_xxx",
|
||||
"rank": 1,
|
||||
"cover_image_url": "https://p3.douyinpic.com/xxx"
|
||||
},
|
||||
"update_time": "2025-10-17 15:30:00"
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 获取统计信息
|
||||
|
||||
**接口地址**
|
||||
```
|
||||
GET /api/rank/stats
|
||||
```
|
||||
|
||||
**功能描述**
|
||||
获取系统统计信息
|
||||
|
||||
**请求参数**
|
||||
无
|
||||
|
||||
**响应示例**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"total_mixes": 1000,
|
||||
"total_playcount": 5000000000,
|
||||
"avg_playcount": 5000000,
|
||||
"max_playcount": 200000000,
|
||||
"min_playcount": 1000,
|
||||
"categories": [
|
||||
{
|
||||
"name": "超热门",
|
||||
"min": 100000000,
|
||||
"count": 5
|
||||
},
|
||||
{
|
||||
"name": "热门",
|
||||
"min": 50000000,
|
||||
"max": 99999999,
|
||||
"count": 20
|
||||
},
|
||||
{
|
||||
"name": "中等",
|
||||
"min": 10000000,
|
||||
"max": 49999999,
|
||||
"count": 150
|
||||
},
|
||||
{
|
||||
"name": "一般",
|
||||
"min": 0,
|
||||
"max": 9999999,
|
||||
"count": 825
|
||||
}
|
||||
],
|
||||
"latest_update": "2025-10-17 15:30:00"
|
||||
},
|
||||
"update_time": "2025-10-17 15:30:00"
|
||||
}
|
||||
```
|
||||
|
||||
### 7. 健康检查
|
||||
|
||||
**接口地址**
|
||||
```
|
||||
GET /api/rank/health
|
||||
```
|
||||
|
||||
**功能描述**
|
||||
检查服务状态和数据库连接
|
||||
|
||||
**请求参数**
|
||||
无
|
||||
|
||||
**响应示例**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "服务正常",
|
||||
"data": {
|
||||
"database": "连接正常",
|
||||
"total_records": 1000,
|
||||
"timestamp": "2025-10-17 15:30:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8. 获取榜单列表
|
||||
|
||||
**接口地址**
|
||||
```
|
||||
GET /api/rank/rankings
|
||||
```
|
||||
|
||||
**功能描述**
|
||||
获取榜单列表,支持按日期和类型查询,支持动态排序
|
||||
|
||||
**请求参数**
|
||||
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|--------|------|------|--------|------|
|
||||
| date | string | 否 | 最新日期 | 日期,格式:YYYY-MM-DD |
|
||||
| type | string | 否 | - | 榜单类型:playcount(播放量榜) / growth(增长榜) / newcomer(新晋榜) |
|
||||
| sort_by | string | 否 | default | 排序方式:default / play_vv_change / play_vv_change_rate / play_vv |
|
||||
| sort_order | string | 否 | desc | 排序顺序:asc / desc |
|
||||
| page | int | 否 | 1 | 页码 |
|
||||
| limit | int | 否 | 50 | 每页数量 |
|
||||
|
||||
**使用示例**
|
||||
```
|
||||
# 获取最新日期的所有榜单
|
||||
GET /api/rank/rankings
|
||||
|
||||
# 获取指定日期的播放量榜
|
||||
GET /api/rank/rankings?date=2025-01-17&type=playcount
|
||||
|
||||
# 按播放量变化排序
|
||||
GET /api/rank/rankings?sort_by=play_vv_change&sort_order=desc
|
||||
|
||||
# 分页获取增长榜
|
||||
GET /api/rank/rankings?type=growth&page=1&limit=20
|
||||
```
|
||||
|
||||
**响应示例**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "获取榜单成功",
|
||||
"data": {
|
||||
"rankings": [
|
||||
{
|
||||
"date": "2025-01-17",
|
||||
"ranking_type": "playcount",
|
||||
"ranking_name": "播放量榜",
|
||||
"description": "按播放量排序的榜单",
|
||||
"data": [
|
||||
{
|
||||
"_id": "674f1234567890abcdef",
|
||||
"mix_name": "热门合集1",
|
||||
"play_vv": 120000000,
|
||||
"rank": 1,
|
||||
"timeline_data": {
|
||||
"play_vv_change": 5000000,
|
||||
"play_vv_change_rate": 4.35
|
||||
}
|
||||
}
|
||||
],
|
||||
"total_count": 100,
|
||||
"current_page_count": 20,
|
||||
"generated_at": "2025-01-17 15:30:00",
|
||||
"version": "1.0",
|
||||
"sort_info": {
|
||||
"sort_by": "default",
|
||||
"sort_order": "desc"
|
||||
}
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"limit": 50,
|
||||
"sort_by": "default",
|
||||
"sort_order": "desc"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 9. 获取可用榜单日期
|
||||
|
||||
**接口地址**
|
||||
```
|
||||
GET /api/rank/rankings/dates
|
||||
```
|
||||
|
||||
**功能描述**
|
||||
获取所有可用的榜单日期列表
|
||||
|
||||
**请求参数**
|
||||
无
|
||||
|
||||
**响应示例**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "获取日期列表成功",
|
||||
"data": {
|
||||
"dates": [
|
||||
"2025-01-17",
|
||||
"2025-01-16",
|
||||
"2025-01-15"
|
||||
],
|
||||
"total": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 10. 获取榜单类型
|
||||
|
||||
**接口地址**
|
||||
```
|
||||
GET /api/rank/rankings/types
|
||||
```
|
||||
|
||||
**功能描述**
|
||||
获取支持的榜单类型及其说明
|
||||
|
||||
**请求参数**
|
||||
无
|
||||
|
||||
**响应示例**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "获取榜单类型成功",
|
||||
"data": {
|
||||
"types": [
|
||||
{
|
||||
"type": "playcount",
|
||||
"description": "播放量榜 - 按播放量排序"
|
||||
},
|
||||
{
|
||||
"type": "growth",
|
||||
"description": "增长榜 - 播放量增长最快"
|
||||
},
|
||||
{
|
||||
"type": "newcomer",
|
||||
"description": "新晋榜 - 新上榜内容"
|
||||
}
|
||||
],
|
||||
"total": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 11. 获取最新榜单
|
||||
|
||||
**接口地址**
|
||||
```
|
||||
GET /api/rank/rankings/latest
|
||||
```
|
||||
|
||||
**功能描述**
|
||||
获取最新日期的所有类型榜单(每个榜单只返回前20条数据)
|
||||
|
||||
**请求参数**
|
||||
无
|
||||
|
||||
**响应示例**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "获取最新榜单成功",
|
||||
"data": {
|
||||
"date": "2025-01-17",
|
||||
"rankings": [
|
||||
{
|
||||
"ranking_type": "playcount",
|
||||
"ranking_name": "播放量榜",
|
||||
"description": "按播放量排序的榜单",
|
||||
"data": [
|
||||
{
|
||||
"_id": "674f1234567890abcdef",
|
||||
"mix_name": "热门合集1",
|
||||
"play_vv": 120000000,
|
||||
"rank": 1
|
||||
}
|
||||
],
|
||||
"total_count": 100,
|
||||
"preview_count": 20
|
||||
}
|
||||
],
|
||||
"total_types": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 12. 获取榜单统计
|
||||
|
||||
**接口地址**
|
||||
```
|
||||
GET /api/rank/rankings/stats
|
||||
```
|
||||
|
||||
**功能描述**
|
||||
获取榜单系统的统计信息
|
||||
|
||||
**请求参数**
|
||||
无
|
||||
|
||||
**响应示例**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "获取榜单统计成功",
|
||||
"data": {
|
||||
"total_rankings": 150,
|
||||
"total_dates": 30,
|
||||
"total_types": 3,
|
||||
"latest_date": "2025-01-17",
|
||||
"earliest_date": "2024-12-18",
|
||||
"date_range": "2024-12-18 至 2025-01-17"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 通用错误格式
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "错误描述",
|
||||
"update_time": "2025-10-17 15:30:00"
|
||||
}
|
||||
```
|
||||
|
||||
### 常见错误
|
||||
- `数据库连接失败`:MongoDB连接异常
|
||||
- `未找到合集信息`:查询的合集不存在
|
||||
- `未找到文章信息`:查询的文章不存在
|
||||
- `请提供搜索关键词`:搜索接口缺少关键词参数
|
||||
- `获取数据失败`:数据查询异常
|
||||
|
||||
## 文章管理接口
|
||||
|
||||
### 1. 获取文章列表
|
||||
|
||||
**接口地址**
|
||||
```
|
||||
GET /api/article/list
|
||||
```
|
||||
|
||||
**功能描述**
|
||||
获取文章列表,支持分页和排序
|
||||
|
||||
**请求参数**
|
||||
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|--------|------|------|--------|------|
|
||||
| page | int | 否 | 1 | 页码 |
|
||||
| limit | int | 否 | 10 | 每页数量 |
|
||||
| sort | string | 否 | created_at | 排序字段 |
|
||||
| order | string | 否 | desc | 排序方向 (asc/desc) |
|
||||
|
||||
**响应示例**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"_id": "68f3ad112ba085e5d676537e",
|
||||
"title": "对于ai影视化的思考",
|
||||
"author_id": "test_user_1",
|
||||
"cover_image": "",
|
||||
"status": "draft",
|
||||
"summary": "AI影视化的发展趋势和思考",
|
||||
"created_at": "2025-10-18 15:06:57",
|
||||
"likes": [],
|
||||
"likes_count": 0
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"current_page": 1,
|
||||
"total_pages": 1,
|
||||
"total_items": 1,
|
||||
"has_next": false,
|
||||
"has_prev": false
|
||||
},
|
||||
"message": "获取文章列表成功",
|
||||
"update_time": "2025-10-18 15:06:57"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 搜索文章
|
||||
|
||||
**接口地址**
|
||||
```
|
||||
GET /api/article/search
|
||||
```
|
||||
|
||||
**功能描述**
|
||||
根据关键词搜索文章
|
||||
|
||||
**请求参数**
|
||||
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|--------|------|------|--------|------|
|
||||
| q | string | 是 | - | 搜索关键词 |
|
||||
| page | int | 否 | 1 | 页码 |
|
||||
| limit | int | 否 | 10 | 每页数量 |
|
||||
|
||||
**响应示例**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"_id": "68f3ad112ba085e5d676537e",
|
||||
"title": "对于ai影视化的思考",
|
||||
"author_id": "test_user_1",
|
||||
"cover_image": "",
|
||||
"status": "draft",
|
||||
"summary": "AI影视化的发展趋势和思考",
|
||||
"created_at": "2025-10-18 15:06:57",
|
||||
"likes": [],
|
||||
"likes_count": 0
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"current_page": 1,
|
||||
"total_pages": 1,
|
||||
"total_items": 1,
|
||||
"has_next": false,
|
||||
"has_prev": false
|
||||
},
|
||||
"message": "搜索文章成功",
|
||||
"update_time": "2025-10-18 15:06:57"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 获取文章详情
|
||||
|
||||
**接口地址**
|
||||
```
|
||||
GET /api/article/detail
|
||||
```
|
||||
|
||||
**功能描述**
|
||||
根据文章ID获取文章详细信息
|
||||
|
||||
**请求参数**
|
||||
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|--------|------|------|--------|------|
|
||||
| id | string | 是 | - | 文章ID |
|
||||
|
||||
**响应示例**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"_id": "68f3ad112ba085e5d676537e",
|
||||
"title": "对于ai影视化的思考",
|
||||
"content": "# AI影视化的思考\n\n在2025年,AI技术在影视行业的应用...",
|
||||
"author_id": "test_user_1",
|
||||
"cover_image": "",
|
||||
"status": "draft",
|
||||
"summary": "AI影视化的发展趋势和思考",
|
||||
"created_at": "2025-10-18 15:06:57",
|
||||
"likes": [],
|
||||
"likes_count": 0
|
||||
},
|
||||
"message": "获取文章详情成功",
|
||||
"update_time": "2025-10-18 15:06:57"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 获取文章统计信息
|
||||
|
||||
**接口地址**
|
||||
```
|
||||
GET /api/article/stats
|
||||
```
|
||||
|
||||
**功能描述**
|
||||
获取文章相关的统计信息
|
||||
|
||||
**请求参数**
|
||||
无
|
||||
|
||||
**响应示例**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"total_articles": 1,
|
||||
"latest_update": "2025-10-18 15:06:57",
|
||||
"status_count": {
|
||||
"draft": 1,
|
||||
"published": 0,
|
||||
"archived": 0
|
||||
}
|
||||
},
|
||||
"message": "获取文章统计成功",
|
||||
"update_time": "2025-10-18 15:06:57"
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 文章服务健康检查
|
||||
|
||||
**接口地址**
|
||||
```
|
||||
GET /api/article/health
|
||||
```
|
||||
|
||||
**功能描述**
|
||||
检查文章服务和数据库连接状态
|
||||
|
||||
**请求参数**
|
||||
无
|
||||
|
||||
**响应示例**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"status": "服务正常",
|
||||
"database": "连接正常",
|
||||
"article_count": 1
|
||||
},
|
||||
"message": "文章服务健康检查通过",
|
||||
"update_time": "2025-10-18 15:06:57"
|
||||
}
|
||||
```
|
||||
|
||||
## 小程序使用建议
|
||||
|
||||
### 1. 分页加载
|
||||
推荐使用分页加载,避免一次性加载过多数据:
|
||||
```javascript
|
||||
// 小程序端示例
|
||||
wx.request({
|
||||
url: 'http://localhost:5000/api/rank/videos',
|
||||
data: {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
sort: 'playcount'
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.data.success) {
|
||||
this.setData({
|
||||
videos: res.data.data,
|
||||
hasNext: res.data.pagination.has_next
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 2. 搜索优化
|
||||
- 使用防抖处理搜索请求
|
||||
- 显示搜索进度和结果数量
|
||||
- 提供搜索建议
|
||||
|
||||
### 3. 图片加载
|
||||
- 使用 `cover_image_url` 作为封面图片
|
||||
- 添加图片加载失败处理
|
||||
|
||||
### 4. 数据更新
|
||||
- 注意 `update_time` 字段,判断数据新鲜度
|
||||
- 合理使用缓存策略
|
||||
- 定期检查服务健康状态
|
||||
|
||||
### 5. 文章管理
|
||||
推荐的文章列表加载方式:
|
||||
```javascript
|
||||
// 小程序端示例 - 文章列表
|
||||
wx.request({
|
||||
url: 'http://localhost:5001/api/article/list',
|
||||
data: {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
sort: 'created_at',
|
||||
order: 'desc'
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.data.success) {
|
||||
this.setData({
|
||||
articles: res.data.data,
|
||||
hasNext: res.data.pagination.has_next
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
文章搜索示例:
|
||||
```javascript
|
||||
// 小程序端示例 - 文章搜索
|
||||
wx.request({
|
||||
url: 'http://localhost:5001/api/article/search',
|
||||
data: {
|
||||
q: 'AI',
|
||||
page: 1,
|
||||
limit: 5
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.data.success) {
|
||||
this.setData({
|
||||
searchResults: res.data.data
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 部署说明
|
||||
|
||||
### 启动服务
|
||||
```bash
|
||||
cd /Users/binghuixiong/剧变AI/rank_backend
|
||||
python3 app.py
|
||||
```
|
||||
|
||||
### 服务信息
|
||||
- 端口:5001
|
||||
- 数据库:MongoDB (localhost:27017)
|
||||
- 数据库名:kemeng_media
|
||||
- 数据更新:每晚24:00自动执行
|
||||
|
||||
### 注意事项
|
||||
- 确保MongoDB服务已启动
|
||||
- 确保网络连接正常
|
||||
- 小程序端需要配置合法域名(生产环境)
|
||||
|
||||
---
|
||||
|
||||
**文档版本**:v4.1
|
||||
**最后更新**:2025-01-18
|
||||
**维护者**:系统自动生成
|
||||
**更新内容**:优化增长排名接口数据源策略,现在专门使用Ranking_storage预计算数据,提升查询性能和数据一致性
|
||||
@ -1 +0,0 @@
|
||||
VITE_API_BASE_URL=http://localhost:8443/api
|
||||
@ -1 +0,0 @@
|
||||
VITE_API_BASE_URL=http://159.75.150.210:8443/api
|
||||
30
frontend/.gitignore
vendored
@ -1,30 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
@ -1,38 +0,0 @@
|
||||
# .
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Recommended Browser Setup
|
||||
|
||||
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
|
||||
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
||||
- Firefox:
|
||||
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
||||
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
@ -1,20 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
||||
<title>Vite App</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,8 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
4328
frontend/package-lock.json
generated
@ -1,26 +0,0 @@
|
||||
{
|
||||
"name": "rank",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.12.2",
|
||||
"bootstrap": "^5.3.0-alpha1",
|
||||
"bootstrap-icons": "^1.13.1",
|
||||
"vue": "^3.5.22",
|
||||
"vue-router": "^4.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"vite": "^7.1.7",
|
||||
"vite-plugin-vue-devtools": "^8.0.2"
|
||||
}
|
||||
}
|
||||
@ -1,986 +0,0 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
// 响应式数据
|
||||
const currentTab = ref('ranking') // 'ranking' 或 'news'
|
||||
const rankingData = ref([])
|
||||
const loading = ref(false)
|
||||
const selectedDate = ref('')
|
||||
const currentPage = ref(1)
|
||||
const totalPages = ref(1)
|
||||
const updateTime = ref('') // 添加更新时间字段
|
||||
const showDatePicker = ref(false) // 控制日期选择器显示
|
||||
const dateOptions = ref([]) // 日期选项列表
|
||||
const selectedCategory = ref('all') // 当前选中的分类
|
||||
const showCommentsSummary = ref(false) // 控制评论总结弹窗显示
|
||||
const currentCommentsSummary = ref('') // 当前显示的评论总结
|
||||
const currentDramaName = ref('') // 当前短剧名称
|
||||
const currentDramaMixId = ref('') // 当前短剧ID
|
||||
|
||||
// 初始化日期为今天
|
||||
const initDate = () => {
|
||||
const today = new Date()
|
||||
selectedDate.value = today.toISOString().split('T')[0]
|
||||
generateDateOptions()
|
||||
}
|
||||
|
||||
// 生成日期选项(今天和往前7天)
|
||||
const generateDateOptions = () => {
|
||||
const options = []
|
||||
const today = new Date()
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const date = new Date(today)
|
||||
date.setDate(today.getDate() - i)
|
||||
|
||||
const value = date.toISOString().split('T')[0]
|
||||
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||
const weekday = weekdays[date.getDay()]
|
||||
|
||||
let label = ''
|
||||
if (i === 0) {
|
||||
label = '今天'
|
||||
} else if (i === 1) {
|
||||
label = '昨天'
|
||||
} else {
|
||||
label = `${i}天前`
|
||||
}
|
||||
|
||||
const display = `${date.getMonth() + 1}月${date.getDate()}日 ${weekday}`
|
||||
|
||||
options.push({
|
||||
value,
|
||||
label,
|
||||
display
|
||||
})
|
||||
}
|
||||
|
||||
dateOptions.value = options
|
||||
}
|
||||
|
||||
// 获取排行榜数据
|
||||
const fetchRankingData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 构建API参数
|
||||
const params = {
|
||||
page: currentPage.value,
|
||||
limit: 100,
|
||||
sort: 'growth',
|
||||
start_date: selectedDate.value,
|
||||
end_date: selectedDate.value
|
||||
}
|
||||
|
||||
// 如果选择了特定分类,添加分类参数
|
||||
if (selectedCategory.value !== 'all') {
|
||||
params.classification_type = selectedCategory.value
|
||||
}
|
||||
|
||||
// const response = await axios.get('http://159.75.150.210:8443/api/rank/videos', { // 远程服务器
|
||||
const response = await axios.get('http://localhost:8443/api/rank/videos', { // 本地服务器
|
||||
params: params
|
||||
})
|
||||
|
||||
if (response.data.success) {
|
||||
rankingData.value = response.data.data
|
||||
totalPages.value = response.data.pagination.pages
|
||||
// 获取后端返回的更新时间
|
||||
updateTime.value = response.data.update_time || ''
|
||||
console.log(`获取${selectedCategory.value === 'all' ? '全部' : selectedCategory.value}分类数据成功,共${response.data.data.length}条`)
|
||||
} else {
|
||||
console.error('获取数据失败:', response.data.message)
|
||||
rankingData.value = []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('API调用失败:', error)
|
||||
rankingData.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前时间
|
||||
const getCurrentTime = () => {
|
||||
const now = new Date()
|
||||
return `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`
|
||||
}
|
||||
|
||||
const getRankClass = (rank) => {
|
||||
if (rank === 1) return 'rank-first'
|
||||
if (rank === 2) return 'rank-second'
|
||||
if (rank === 3) return 'rank-third'
|
||||
return 'rank-normal'
|
||||
}
|
||||
|
||||
// 格式化播放量
|
||||
const formatPlayCount = (count) => {
|
||||
if (!count) return '0'
|
||||
if (count >= 100000000) {
|
||||
return (count / 100000000).toFixed(1) + '亿'
|
||||
} else if (count >= 10000) {
|
||||
return (count / 10000).toFixed(1) + '万'
|
||||
}
|
||||
return count.toString()
|
||||
}
|
||||
|
||||
// 格式化增长数据
|
||||
const formatGrowth = (item) => {
|
||||
const timelineData = item.timeline_data || {}
|
||||
const change = timelineData.play_vv_change || 0
|
||||
const changeRate = timelineData.play_vv_change_rate || 0
|
||||
|
||||
if (change > 0) {
|
||||
return `${formatPlayCount(change)}`
|
||||
}
|
||||
return '暂无数据'
|
||||
}
|
||||
|
||||
// 切换标签页
|
||||
const switchTab = (tab) => {
|
||||
currentTab.value = tab
|
||||
if (tab === 'ranking') {
|
||||
fetchRankingData()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取图片源地址
|
||||
const getImageSrc = (item) => {
|
||||
// 优先使用 cover_image_url
|
||||
if (item.cover_image_url) {
|
||||
return item.cover_image_url
|
||||
}
|
||||
|
||||
// 如果有备用链接,使用第一个
|
||||
if (item.cover_backup_urls && item.cover_backup_urls.length > 0) {
|
||||
return item.cover_backup_urls[0]
|
||||
}
|
||||
|
||||
// 最后使用占位符
|
||||
return '/placeholder-poster.svg'
|
||||
}
|
||||
|
||||
// 处理图片加载错误
|
||||
const handleImageError = (event, item) => {
|
||||
const img = event.target
|
||||
console.log('图片加载失败:', img.src, '视频:', item.title)
|
||||
|
||||
// 如果当前显示的是主链接,尝试备用链接
|
||||
if (img.src === item.cover_image_url && item.cover_backup_urls && item.cover_backup_urls.length > 0) {
|
||||
console.log('尝试备用链接:', item.cover_backup_urls[0])
|
||||
// 尝试第一个备用链接
|
||||
img.src = item.cover_backup_urls[0]
|
||||
return
|
||||
}
|
||||
|
||||
// 如果当前显示的是第一个备用链接,尝试其他备用链接
|
||||
if (item.cover_backup_urls && item.cover_backup_urls.length > 1) {
|
||||
const currentIndex = item.cover_backup_urls.indexOf(img.src)
|
||||
if (currentIndex >= 0 && currentIndex < item.cover_backup_urls.length - 1) {
|
||||
console.log('尝试下一个备用链接:', item.cover_backup_urls[currentIndex + 1])
|
||||
img.src = item.cover_backup_urls[currentIndex + 1]
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 所有链接都失败,使用占位符
|
||||
console.log('使用占位符图片')
|
||||
img.src = '/placeholder-poster.svg'
|
||||
}
|
||||
|
||||
// 日期改变处理
|
||||
const onDateChange = () => {
|
||||
currentPage.value = 1
|
||||
fetchRankingData()
|
||||
}
|
||||
|
||||
// 格式化显示日期
|
||||
const formatDisplayDate = (dateStr) => {
|
||||
if (!dateStr) return '选择日期'
|
||||
|
||||
const date = new Date(dateStr)
|
||||
const today = new Date()
|
||||
const diffTime = today.getTime() - date.getTime()
|
||||
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24))
|
||||
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`
|
||||
|
||||
}
|
||||
|
||||
// 切换日期选择器显示状态
|
||||
const toggleDatePicker = () => {
|
||||
showDatePicker.value = !showDatePicker.value
|
||||
}
|
||||
|
||||
// 关闭日期选择器
|
||||
const closeDatePicker = () => {
|
||||
showDatePicker.value = false
|
||||
}
|
||||
|
||||
// 选择日期
|
||||
const selectDate = (dateValue) => {
|
||||
selectedDate.value = dateValue
|
||||
showDatePicker.value = false
|
||||
onDateChange()
|
||||
}
|
||||
|
||||
// 切换分类
|
||||
const switchCategory = (category) => {
|
||||
selectedCategory.value = category
|
||||
currentPage.value = 1 // 重置页码
|
||||
fetchRankingData() // 重新获取数据
|
||||
console.log(`切换到分类: ${category}`)
|
||||
}
|
||||
|
||||
// 格式化日期显示(用于日榜标题)
|
||||
const formatDateTitle = (dateStr) => {
|
||||
if (!dateStr) return '日榜 2025年10月19日/周日'
|
||||
|
||||
const date = new Date(dateStr)
|
||||
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||
const weekday = weekdays[date.getDay()]
|
||||
const year = date.getFullYear()
|
||||
const month = date.getMonth() + 1
|
||||
const day = date.getDate()
|
||||
|
||||
return `日榜 ${year}年${month}月${day}日/${weekday}`
|
||||
}
|
||||
|
||||
// 获取排名徽章样式类
|
||||
const getRankBadgeClass = (rank) => {
|
||||
if (rank === 1) return 'rank-gold'
|
||||
if (rank === 2) return 'rank-silver'
|
||||
if (rank === 3) return 'rank-bronze'
|
||||
return 'rank-normal'
|
||||
}
|
||||
|
||||
// 跳转到后台管理
|
||||
// const goToAdmin = () => {
|
||||
// router.push('/admin')
|
||||
// }
|
||||
|
||||
// 获取评论总结(优先使用 mix_id)- 改为直接跳转到详情页
|
||||
const fetchCommentsSummary = async (item, event) => {
|
||||
// 阻止事件冒泡,避免触发卡片点击
|
||||
if (event) {
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
// 直接跳转到详情页,并定位到评论区域
|
||||
const dramaId = item.mix_id || item._id
|
||||
router.push(`/drama/${dramaId}#comments`)
|
||||
}
|
||||
|
||||
// 关闭评论总结弹窗
|
||||
const closeCommentsSummary = () => {
|
||||
showCommentsSummary.value = false
|
||||
currentCommentsSummary.value = ''
|
||||
currentDramaName.value = ''
|
||||
currentDramaMixId.value = ''
|
||||
}
|
||||
|
||||
// 跳转到短剧详情页
|
||||
const goToDramaDetail = (item) => {
|
||||
// 使用 mix_id 作为路由参数
|
||||
const dramaId = item.mix_id || item._id
|
||||
router.push(`/drama/${dramaId}`)
|
||||
}
|
||||
|
||||
// 页面加载时初始化
|
||||
onMounted(() => {
|
||||
initDate()
|
||||
fetchRankingData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app">
|
||||
<!-- 路由视图 -->
|
||||
<router-view v-if="route.path !== '/'" />
|
||||
|
||||
<!-- 主容器 - 仅在首页显示 -->
|
||||
<div v-if="route.path === '/'" class="main-container">
|
||||
<!-- 顶部横幅区域(按设计稿) -->
|
||||
<div class="top-banner">
|
||||
<div class="banner-inner">
|
||||
<img src="./images/mhru18yf-f5p3yze.svg" class="banner-main-title" />
|
||||
<p class="banner-subtitle">基于抖音端原生播放增量排序</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分类标签区域 -->
|
||||
<!-- <div class="category-section">
|
||||
<div
|
||||
class="category-tab"
|
||||
:class="{ active: selectedCategory === 'all' }"
|
||||
@click="switchCategory('all')"
|
||||
>
|
||||
<span>全部</span>
|
||||
</div>
|
||||
<div
|
||||
class="category-tab"
|
||||
:class="{ active: selectedCategory === 'novel' }"
|
||||
@click="switchCategory('novel')"
|
||||
>
|
||||
<span>小说</span>
|
||||
</div>
|
||||
<div
|
||||
class="category-tab"
|
||||
:class="{ active: selectedCategory === 'anime' }"
|
||||
@click="switchCategory('anime')"
|
||||
>
|
||||
<span>动漫</span>
|
||||
</div>
|
||||
<div
|
||||
class="category-tab"
|
||||
:class="{ active: selectedCategory === 'drama' }"
|
||||
@click="switchCategory('drama')"
|
||||
>
|
||||
<span>短剧</span>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- 排行榜内容区域 -->
|
||||
<div class="ranking-content">
|
||||
<!-- 日期显示区域 -->
|
||||
<div class="date-section">
|
||||
<p class="date-title">{{ formatDateTitle(selectedDate) }}</p>
|
||||
<div class="date-dropdown-icon" @click="toggleDatePicker"></div>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
|
||||
<!-- 排行榜列表 -->
|
||||
<div v-else class="ranking-list">
|
||||
<div
|
||||
v-for="(item, index) in rankingData"
|
||||
:key="item._id || index"
|
||||
class="ranking-item"
|
||||
@click="goToDramaDetail(item)"
|
||||
>
|
||||
<!-- 排名标识 -->
|
||||
<div class="rank-badge" :class="getRankBadgeClass(index + 1)">
|
||||
<span class="rank-number">{{ index + 1 }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 海报图片 -->
|
||||
<div class="poster-container">
|
||||
<img
|
||||
:src="getImageSrc(item)"
|
||||
:alt="item.title || item.mix_name"
|
||||
@error="handleImageError($event, item)"
|
||||
class="poster-image"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 内容信息区域 -->
|
||||
<div class="content-area">
|
||||
<!-- 剧名 -->
|
||||
<h3 class="drama-title">{{ item.title || item.mix_name || '奶团' }}</h3>
|
||||
|
||||
<!-- 详细信息 -->
|
||||
<div class="drama-details">
|
||||
<div class="detail-icons">
|
||||
<img src="./images/剧场名icon.svg" alt="剧场名" class="detail-icon" />
|
||||
<img src="./images/承制icon.svg" alt="承制" class="detail-icon" />
|
||||
<img src="./images/版权icon.svg" alt="版权" class="detail-icon" />
|
||||
</div>
|
||||
<div class="detail-text">
|
||||
<p>剧场名:{{ item.series_author || '' }}</p>
|
||||
<p>承制:{{ item.Manufacturing_Field || '' }}</p>
|
||||
<p>版权:{{ item.Copyright_field || '' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据统计 -->
|
||||
<div class="stats-row">
|
||||
<div class="stat-item">
|
||||
<img src="./images/播放icon.svg" alt="播放" class="stat-icon" />
|
||||
<span class="stat-value">{{ formatPlayCount(item.play_vv) || '9999W' }}</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<img src="./images/点赞icon.svg" alt="点赞" class="stat-icon" />
|
||||
<span class="stat-value">{{ item.total_likes_formatted || '0' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 增长数据 -->
|
||||
<div class="growth-section">
|
||||
<div>
|
||||
<img src="./images/热度icon.svg" alt="热度" class="growth-icon" />
|
||||
<span class="growth-value">{{ formatGrowth(item) || '300W' }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 评论总结按钮 - 只有当有评论总结时才显示 -->
|
||||
<button
|
||||
v-if="item.comments_summary"
|
||||
class="comments-summary-btn"
|
||||
@click="fetchCommentsSummary(item, $event)"
|
||||
title="查看用户评论AI总结"
|
||||
>
|
||||
用户评论总结
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="rankingData.length === 0" class="empty-state">
|
||||
<p>暂无排行榜数据</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 日期选择弹窗 -->
|
||||
<div v-if="showDatePicker" class="date-picker-overlay" @click="closeDatePicker">
|
||||
<div class="date-picker-popup" @click.stop>
|
||||
<div class="date-picker-header">
|
||||
<h3>选择日期</h3>
|
||||
<button class="close-btn" @click="closeDatePicker">×</button>
|
||||
</div>
|
||||
<div class="date-list">
|
||||
<div
|
||||
v-for="date in dateOptions"
|
||||
:key="date.value"
|
||||
class="date-option"
|
||||
:class="{ active: selectedDate === date.value }"
|
||||
@click="selectDate(date.value)"
|
||||
>
|
||||
<span class="date-label">{{ date.label }}</span>
|
||||
<span class="date-value">{{ date.display }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 全局样式 */
|
||||
.app {
|
||||
min-height: 100vh;
|
||||
background: #ebedf2;
|
||||
font-family: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', SimHei, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
/* 主容器 */
|
||||
.main-container {
|
||||
max-width: 428px;
|
||||
margin: 0 auto;
|
||||
background: #ebedf2;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 顶部横幅(按设计稿) */
|
||||
.top-banner {
|
||||
position: relative;
|
||||
height: 180px;
|
||||
/* 背景图改为项目内资源路径 */
|
||||
background-image: url('./images/top_bg.png');
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.top-banner::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(to bottom, rgba(0,0,0,0.0) 0%, rgba(0,0,0,0.05) 60%, rgba(0,0,0,0.1) 100%);
|
||||
}
|
||||
|
||||
.banner-inner {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
.banner-main-title {
|
||||
width: 136px;
|
||||
}
|
||||
.banner-subtitle {
|
||||
margin-top: 12px;
|
||||
letter-spacing: 1px;
|
||||
color: #ffffff;
|
||||
font-family: ABeeZee, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", SimHei, Arial, Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 横幅区域 */
|
||||
.banner-section {
|
||||
margin: 20px 16px;
|
||||
background: linear-gradient(135deg, #4a90e2 0%, #357abd 100%);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.banner-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 装饰分隔线 */
|
||||
.divider-dots {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 20px 0;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.divider-dots::before,
|
||||
.divider-dots::after {
|
||||
content: '';
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: #4a90e2;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* 日期显示区域 */
|
||||
.date-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.date-title {
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
margin: 8px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.date-dropdown-icon {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-top: 6px solid #4a90e2;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 分类标签区域 */
|
||||
.category-section {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
margin: 20px 16px;
|
||||
}
|
||||
|
||||
.category-tab {
|
||||
padding: 8px 20px;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.category-tab:not(.active) {
|
||||
background: #d1d5db;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.category-tab.active {
|
||||
background: #4a90e2;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 排行榜内容区域 */
|
||||
.ranking-content {
|
||||
/* 作为白色圆角卡片容器 */
|
||||
background: #ffffff;
|
||||
/* 仅顶部圆角 */
|
||||
border-radius: 24px 24px 0 0;
|
||||
/* 移除两侧内边距与外边距 */
|
||||
padding: 0;
|
||||
margin: -24px 0 16px;
|
||||
/* 提高层级,确保顶部圆角可见 */
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #4a90e2;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 排行榜列表 */
|
||||
.ranking-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
min-height: 120px;
|
||||
padding: 12px 0 ;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #E1E3E5;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.ranking-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
/* 排名徽章 */
|
||||
.rank-badge {
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
left: -1px;
|
||||
width: 24px;
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.rank-badge.rank-gold {
|
||||
background: linear-gradient(135deg, #ffd700, #ffed4e);
|
||||
box-shadow: 0 2px 8px rgba(255, 215, 0, 0.4);
|
||||
}
|
||||
|
||||
.rank-badge.rank-silver {
|
||||
background: linear-gradient(135deg, #c0c0c0, #e8e8e8);
|
||||
box-shadow: 0 2px 8px rgba(192, 192, 192, 0.4);
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.rank-badge.rank-bronze {
|
||||
background: linear-gradient(135deg, #cd7f32, #daa520);
|
||||
box-shadow: 0 2px 8px rgba(205, 127, 50, 0.4);
|
||||
}
|
||||
|
||||
.rank-badge.rank-normal {
|
||||
background: #6b7280;
|
||||
box-shadow: 0 2px 8px rgba(107, 114, 128, 0.3);
|
||||
}
|
||||
|
||||
/* 海报容器 */
|
||||
.poster-container {
|
||||
width: 84px;
|
||||
height: 112px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.poster-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.content-area {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.drama-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin: 0 0 8px 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* 详细信息 */
|
||||
.drama-details {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.detail-icons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.detail-icon {
|
||||
margin: 1px 0;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.detail-text p {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin: 0 0 2px 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* 数据统计行 */
|
||||
.stats-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 12px;
|
||||
color: #374151;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 增长数据 */
|
||||
.growth-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 2px;
|
||||
flex-shrink: 0;
|
||||
min-width: 60px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.growth-section > div:first-child {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.growth-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: white;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.growth-value {
|
||||
color: #ef4444;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.comment-summary p {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #6b7280;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 日期选择弹窗 */
|
||||
.date-picker-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.date-picker-popup {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
padding: 0;
|
||||
max-width: 400px;
|
||||
width: 90%;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.date-picker-header {
|
||||
background: #4a90e2;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.date-picker-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.date-list {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.date-option {
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s ease;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.date-option:hover {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.date-option.active {
|
||||
background: #4a90e2;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.date-option:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.date-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.date-value {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.date-option.active .date-value {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
/* 评论总结按钮样式 */
|
||||
.comments-summary-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #333;
|
||||
font-size: 10px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
margin-top: 78px;
|
||||
display: block;
|
||||
text-align: left;
|
||||
transition: color 0.2s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.comments-summary-btn:hover {
|
||||
color: #666;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 480px) {
|
||||
.main-container {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.banner-section {
|
||||
margin: 20px 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.ranking-content {
|
||||
margin-top: -12px;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.ranking-item {
|
||||
padding: 12px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.poster-container {
|
||||
width: 50px;
|
||||
height: 70px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,683 +0,0 @@
|
||||
<template>
|
||||
<div class="applications-page">
|
||||
<div class="header">
|
||||
<div class="header-top">
|
||||
<button class="back-btn" @click="goBack">←</button>
|
||||
<h1>申请管理</h1>
|
||||
</div>
|
||||
<div class="filters">
|
||||
<button
|
||||
:class="['filter-btn', { active: currentFilter === 'all' }]"
|
||||
@click="changeFilter('all')"
|
||||
>
|
||||
全部 ({{ stats.total }})
|
||||
</button>
|
||||
<button
|
||||
:class="['filter-btn', { active: currentFilter === 'pending' }]"
|
||||
@click="changeFilter('pending')"
|
||||
>
|
||||
待审核 ({{ stats.pending }})
|
||||
</button>
|
||||
<button
|
||||
:class="['filter-btn', { active: currentFilter === 'approved' }]"
|
||||
@click="changeFilter('approved')"
|
||||
>
|
||||
已通过 ({{ stats.approved }})
|
||||
</button>
|
||||
<button
|
||||
:class="['filter-btn', { active: currentFilter === 'rejected' }]"
|
||||
@click="changeFilter('rejected')"
|
||||
>
|
||||
已拒绝 ({{ stats.rejected }})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table class="applications-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>短剧名称</th>
|
||||
<th>申请类型</th>
|
||||
<th>公司名称</th>
|
||||
<th>提交时间</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="loading">
|
||||
<td colspan="6" class="loading-cell">加载中...</td>
|
||||
</tr>
|
||||
<tr v-else-if="applications.length === 0">
|
||||
<td colspan="6" class="empty-cell">暂无申请</td>
|
||||
</tr>
|
||||
<tr v-else v-for="app in applications" :key="app.application_id">
|
||||
<td>{{ app.drama_name }}</td>
|
||||
<td>
|
||||
<span :class="['type-badge', app.field_type]">
|
||||
{{ app.field_type_label }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ app.company_name }}</td>
|
||||
<td>{{ app.submit_time }}</td>
|
||||
<td>
|
||||
<span :class="['status-badge', app.status]">
|
||||
{{ app.status_label }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<button class="action-btn view" @click="viewDetail(app)">查看详情</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<div v-if="showDetailModal" class="modal-overlay" @click="closeDetail">
|
||||
<div class="modal-content" @click.stop>
|
||||
<div class="modal-header">
|
||||
<h2>申请详情</h2>
|
||||
<button class="close-btn" @click="closeDetail">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body" v-if="currentApplication">
|
||||
<div class="detail-section">
|
||||
<h3>基本信息</h3>
|
||||
<div class="detail-row">
|
||||
<span class="label">短剧名称:</span>
|
||||
<span class="value">{{ currentApplication.drama_name }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="label">申请类型:</span>
|
||||
<span class="value">{{ currentApplication.field_type_label }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="label">公司名称:</span>
|
||||
<span class="value">{{ currentApplication.company_name }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="label">补充说明:</span>
|
||||
<span class="value">{{ currentApplication.description || '无' }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="label">提交时间:</span>
|
||||
<span class="value">{{ currentApplication.submit_time }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="label">状态:</span>
|
||||
<span :class="['status-badge', currentApplication.status]">
|
||||
{{ currentApplication.status_label }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h3>证明材料</h3>
|
||||
<div class="files-grid">
|
||||
<a
|
||||
v-for="(url, index) in currentApplication.tos_file_urls"
|
||||
:key="index"
|
||||
:href="url"
|
||||
target="_blank"
|
||||
class="file-link"
|
||||
>
|
||||
<div class="file-preview">
|
||||
<img v-if="isImage(url)" :src="url" alt="证明材料" />
|
||||
<div v-else class="file-icon">
|
||||
<span>{{ getFileExtension(url) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="file-name">文件 {{ index + 1 }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentApplication.status === 'rejected'" class="detail-section">
|
||||
<h3>拒绝理由</h3>
|
||||
<p class="reject-reason">{{ currentApplication.reject_reason }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer" v-if="currentApplication && currentApplication.status === 'pending'">
|
||||
<button class="action-btn reject" @click="showRejectInput = true" v-if="!showRejectInput">
|
||||
拒绝
|
||||
</button>
|
||||
<div v-if="showRejectInput" class="reject-input-group">
|
||||
<input
|
||||
v-model="rejectReason"
|
||||
type="text"
|
||||
placeholder="请输入拒绝理由"
|
||||
class="reject-input"
|
||||
/>
|
||||
<button class="action-btn reject" @click="handleReject">确认拒绝</button>
|
||||
<button class="action-btn cancel" @click="showRejectInput = false">取消</button>
|
||||
</div>
|
||||
<button class="action-btn approve" @click="handleApprove">通过</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import axios from 'axios'
|
||||
import { API_BASE_URL } from './api/base'
|
||||
|
||||
const router = useRouter()
|
||||
// API基础URL(从环境变量导入)
|
||||
|
||||
// 响应式数据
|
||||
const applications = ref([])
|
||||
const loading = ref(false)
|
||||
const currentFilter = ref('all')
|
||||
const showDetailModal = ref(false)
|
||||
const currentApplication = ref(null)
|
||||
const showRejectInput = ref(false)
|
||||
const rejectReason = ref('')
|
||||
|
||||
// 统计数据
|
||||
const stats = ref({
|
||||
total: 0,
|
||||
pending: 0,
|
||||
approved: 0,
|
||||
rejected: 0
|
||||
})
|
||||
|
||||
// 获取申请列表
|
||||
const fetchApplications = async (status = 'all') => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/rank/claim/applications`, {
|
||||
params: { status, page: 1, limit: 100 }
|
||||
})
|
||||
|
||||
if (response.data.success) {
|
||||
applications.value = response.data.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取申请列表失败:', error)
|
||||
alert('获取申请列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取统计数据
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
const [allRes, pendingRes, approvedRes, rejectedRes] = await Promise.all([
|
||||
axios.get(`${API_BASE_URL}/rank/claim/applications`, { params: { status: 'all', limit: 1 } }),
|
||||
axios.get(`${API_BASE_URL}/rank/claim/applications`, { params: { status: 'pending', limit: 1 } }),
|
||||
axios.get(`${API_BASE_URL}/rank/claim/applications`, { params: { status: 'approved', limit: 1 } }),
|
||||
axios.get(`${API_BASE_URL}/rank/claim/applications`, { params: { status: 'rejected', limit: 1 } })
|
||||
])
|
||||
|
||||
stats.value = {
|
||||
total: allRes.data.pagination?.total || 0,
|
||||
pending: pendingRes.data.pagination?.total || 0,
|
||||
approved: approvedRes.data.pagination?.total || 0,
|
||||
rejected: rejectedRes.data.pagination?.total || 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取统计数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 切换筛选
|
||||
const changeFilter = (filter) => {
|
||||
currentFilter.value = filter
|
||||
fetchApplications(filter)
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const viewDetail = async (app) => {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE_URL}/rank/claim/application/${app.application_id}`)
|
||||
if (response.data.success) {
|
||||
currentApplication.value = response.data.data
|
||||
showDetailModal.value = true
|
||||
showRejectInput.value = false
|
||||
rejectReason.value = ''
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取申请详情失败:', error)
|
||||
alert('获取申请详情失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭详情
|
||||
const closeDetail = () => {
|
||||
showDetailModal.value = false
|
||||
currentApplication.value = null
|
||||
showRejectInput.value = false
|
||||
rejectReason.value = ''
|
||||
}
|
||||
|
||||
// 通过申请
|
||||
const handleApprove = async () => {
|
||||
if (!confirm('确认通过该申请吗?')) return
|
||||
|
||||
try {
|
||||
const response = await axios.post(`${API_BASE_URL}/rank/claim/review`, {
|
||||
application_id: currentApplication.value.application_id,
|
||||
action: 'approve',
|
||||
reviewer: 'admin'
|
||||
})
|
||||
|
||||
if (response.data.success) {
|
||||
alert('申请已通过')
|
||||
closeDetail()
|
||||
fetchApplications(currentFilter.value)
|
||||
fetchStats()
|
||||
} else {
|
||||
alert('操作失败:' + response.data.message)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('审核失败:', error)
|
||||
alert('审核失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
// 拒绝申请
|
||||
const handleReject = async () => {
|
||||
if (!rejectReason.value.trim()) {
|
||||
alert('请输入拒绝理由')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.post(`${API_BASE_URL}/rank/claim/review`, {
|
||||
application_id: currentApplication.value.application_id,
|
||||
action: 'reject',
|
||||
reject_reason: rejectReason.value,
|
||||
reviewer: 'admin'
|
||||
})
|
||||
|
||||
if (response.data.success) {
|
||||
alert('申请已拒绝')
|
||||
closeDetail()
|
||||
fetchApplications(currentFilter.value)
|
||||
fetchStats()
|
||||
} else {
|
||||
alert('操作失败:' + response.data.message)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('审核失败:', error)
|
||||
alert('审核失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否为图片
|
||||
const isImage = (url) => {
|
||||
return /\.(jpg|jpeg|png|gif)$/i.test(url)
|
||||
}
|
||||
|
||||
// 获取文件扩展名
|
||||
const getFileExtension = (url) => {
|
||||
const match = url.match(/\.([^.]+)$/)
|
||||
return match ? match[1].toUpperCase() : 'FILE'
|
||||
}
|
||||
|
||||
// 返回管理后台
|
||||
const goBack = () => {
|
||||
router.push('/admin')
|
||||
}
|
||||
|
||||
// 页面加载时获取数据
|
||||
onMounted(() => {
|
||||
fetchApplications('all')
|
||||
fetchStats()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.applications-page {
|
||||
padding: 12px;
|
||||
max-width: 428px;
|
||||
margin: 0 auto;
|
||||
background: #ebedf2;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.header-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
background: white;
|
||||
border: none;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #374151;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background: #f3f4f6;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 18px;
|
||||
margin: 0;
|
||||
color: #1f2937;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #e5e7eb;
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.filter-btn:hover {
|
||||
background: #f3f4f6;
|
||||
}
|
||||
|
||||
.filter-btn.active {
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.applications-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.applications-table th {
|
||||
background: #f9fafb;
|
||||
padding: 8px 10px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.applications-table td {
|
||||
padding: 8px 10px;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.loading-cell,
|
||||
.empty-cell {
|
||||
text-align: center;
|
||||
color: #9ca3af;
|
||||
padding: 40px !important;
|
||||
}
|
||||
|
||||
.type-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.type-badge.copyright {
|
||||
background: #dbeafe;
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
.type-badge.manufacturing {
|
||||
background: #fce7f3;
|
||||
color: #be123c;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge.pending {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.status-badge.approved {
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
.status-badge.rejected {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 5px 10px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.action-btn.view {
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.view:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.action-btn.approve {
|
||||
background: #10b981;
|
||||
color: white;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.action-btn.approve:hover {
|
||||
background: #059669;
|
||||
}
|
||||
|
||||
.action-btn.reject {
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-btn.reject:hover {
|
||||
background: #dc2626;
|
||||
}
|
||||
|
||||
.action-btn.cancel {
|
||||
background: #6b7280;
|
||||
color: white;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: #6b7280;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.detail-section h3 {
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
color: #374151;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.detail-row .label {
|
||||
width: 80px;
|
||||
color: #6b7280;
|
||||
font-size: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-row .value {
|
||||
flex: 1;
|
||||
color: #1f2937;
|
||||
font-size: 12px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.files-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.file-link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.file-preview {
|
||||
width: 100%;
|
||||
height: 90px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background: #f3f4f6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.file-preview img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
font-size: 11px;
|
||||
color: #6b7280;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-size: 11px;
|
||||
color: #6b7280;
|
||||
display: block;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.reject-reason {
|
||||
padding: 10px;
|
||||
background: #fef2f2;
|
||||
border-left: 3px solid #ef4444;
|
||||
color: #991b1b;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 12px 16px;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.reject-input-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.reject-input {
|
||||
flex: 1;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||