mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
488 lines
13 KiB
Vue
488 lines
13 KiB
Vue
<template>
|
|
<div class="custom-navbar-history-list">
|
|
<div class="header">
|
|
<div class="header-row">
|
|
<div class="search">
|
|
<TextBox v-model="search" placeholder="搜索" linear></TextBox>
|
|
</div>
|
|
<div class="operations">
|
|
<div class="operation" @click="toggleHistoryPause">
|
|
<VButton v-if="!paused" title="暂停记录历史" round>
|
|
<VIcon icon="mdi-pause" :size="14"></VIcon>
|
|
</VButton>
|
|
<VButton v-else title="继续记录历史" round>
|
|
<VIcon icon="mdi-play" :size="14"></VIcon>
|
|
</VButton>
|
|
</div>
|
|
<a class="operation" target="_blank" href="https://www.bilibili.com/account/history">
|
|
<VButton title="查看更多" round>
|
|
<VIcon icon="mdi-dots-horizontal" :size="18"></VIcon>
|
|
</VButton>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="header-row">
|
|
<div class="row-title">过滤:</div>
|
|
<div class="type-filters">
|
|
<div v-for="t of types" :key="t.name" class="type-filter">
|
|
<RadioButton
|
|
:title="(t.checked ? '不显示' : '显示') + t.displayName"
|
|
:class="{ checked: t.checked }"
|
|
:checked="t.checked"
|
|
:disabled="loading"
|
|
@change="toggleTypeFilter(t)"
|
|
>
|
|
{{ t.displayName }}
|
|
</RadioButton>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="content">
|
|
<VLoading v-if="loading"></VLoading>
|
|
<VEmpty v-else-if="!loading && groups.length === 0"></VEmpty>
|
|
<transition-group v-else name="cards" tag="div" class="cards">
|
|
<div v-for="g of groups" :key="g.name" class="time-group">
|
|
<div class="time-group-name">
|
|
{{ g.name }}
|
|
</div>
|
|
<transition-group name="time-group" tag="div" class="time-group-items">
|
|
<div v-for="h of g.items" :key="h.id" class="time-group-item">
|
|
<a class="cover-container" target="_blank" :href="h.url">
|
|
<DpiImage
|
|
class="cover"
|
|
:src="h.cover"
|
|
:size="{ width: 160, height: 110 }"
|
|
placeholder-image
|
|
></DpiImage>
|
|
<div
|
|
v-if="h.progress"
|
|
class="progress"
|
|
:style="{ width: h.progress * 100 + '%' }"
|
|
></div>
|
|
<div v-if="h.progressText" class="floating progress-number">
|
|
{{ h.progress >= 1 ? '已看完' : h.progressText }}
|
|
</div>
|
|
<div
|
|
v-if="h.liveStatus !== undefined"
|
|
class="floating duration live-status"
|
|
:class="{ on: h.liveStatus === 1 }"
|
|
>
|
|
{{ h.liveStatus === 1 ? '直播中' : '未开播' }}
|
|
</div>
|
|
<div v-if="h.durationText" class="floating duration">{{ h.durationText }}</div>
|
|
</a>
|
|
<a class="title" target="_blank" :href="h.url" :title="h.title">{{
|
|
h.title || h.upName + '的直播间'
|
|
}}</a>
|
|
<a
|
|
class="up"
|
|
target="_blank"
|
|
:href="h.type === 'pgc' ? h.url : 'https://space.bilibili.com/' + h.upID"
|
|
:title="h.upName"
|
|
>
|
|
<DpiImage
|
|
v-if="h.upFaceUrl"
|
|
class="up-face"
|
|
:size="18"
|
|
:src="h.upFaceUrl"
|
|
></DpiImage>
|
|
<div class="up-name">{{ h.upName }}</div>
|
|
</a>
|
|
<div v-if="h.timeText" class="time" :title="new Date(h.viewAt).toLocaleString()">
|
|
{{ h.timeText }}
|
|
</div>
|
|
</div>
|
|
</transition-group>
|
|
</div>
|
|
<ScrollTrigger
|
|
v-if="canNextPage"
|
|
key="scroll-trigger"
|
|
@trigger="nextPage()"
|
|
></ScrollTrigger>
|
|
</transition-group>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script lang="ts">
|
|
import { bilibiliApi, getJsonWithCredentials, postTextWithCredentials } from '@/core/ajax'
|
|
import { formData, getCsrf } from '@/core/utils'
|
|
import { descendingSort } from '@/core/utils/sort'
|
|
import {
|
|
DpiImage,
|
|
RadioButton,
|
|
ScrollTrigger,
|
|
TextBox,
|
|
VButton,
|
|
VEmpty,
|
|
VIcon,
|
|
VLoading,
|
|
} from '@/ui'
|
|
|
|
import { popperMixin } from '../mixins'
|
|
import type { HistoryItem, TypeFilter } from './types'
|
|
import { getHistoryItems, group, HistoryType, types } from './types'
|
|
|
|
export default Vue.extend({
|
|
components: {
|
|
VButton,
|
|
VIcon,
|
|
RadioButton,
|
|
TextBox,
|
|
VLoading,
|
|
VEmpty,
|
|
ScrollTrigger,
|
|
DpiImage,
|
|
},
|
|
mixins: [popperMixin],
|
|
data() {
|
|
return {
|
|
types,
|
|
search: '',
|
|
viewTime: 0,
|
|
cards: [],
|
|
groups: [],
|
|
loading: true,
|
|
hasMorePage: true,
|
|
paused: false,
|
|
}
|
|
},
|
|
computed: {
|
|
canNextPage() {
|
|
return this.search === '' && !this.loading && this.hasMorePage
|
|
},
|
|
},
|
|
watch: {
|
|
search: lodash.debounce(function search() {
|
|
this.reloadHistoryItems()
|
|
}, 200),
|
|
},
|
|
async created() {
|
|
try {
|
|
await Promise.all([this.nextPage(), this.updateHistoryPauseState()])
|
|
} finally {
|
|
this.loading = false
|
|
}
|
|
},
|
|
methods: {
|
|
toggleTypeFilter(typeFilter: TypeFilter) {
|
|
types.forEach(t => (t.checked = t.name === typeFilter.name))
|
|
this.reloadHistoryItems()
|
|
},
|
|
async reloadHistoryItems() {
|
|
this.viewTime = 0
|
|
this.hasMorePage = true
|
|
this.loading = true
|
|
try {
|
|
await this.nextPage()
|
|
} finally {
|
|
this.loading = false
|
|
}
|
|
},
|
|
filterFunc(item: HistoryItem) {
|
|
const isAllType = types.find(it => it.name === HistoryType.All).checked
|
|
if (!isAllType && types.some(t => t.name === item.type && !t.checked)) {
|
|
return false
|
|
}
|
|
if (
|
|
!item.title.toLowerCase().includes(this.search.toLowerCase()) &&
|
|
!item.upName.toLowerCase().includes(this.search.toLowerCase())
|
|
) {
|
|
return false
|
|
}
|
|
return true
|
|
},
|
|
updateGroups() {
|
|
this.groups = group(this.cards.filter(this.filterFunc))
|
|
},
|
|
async nextPage() {
|
|
const items = await getHistoryItems(
|
|
this.viewTime,
|
|
types.find(t => t.checked),
|
|
)
|
|
const cards: HistoryItem[] = lodash.uniqBy(
|
|
this.cards.concat(items).sort(descendingSort((item: HistoryItem) => item.viewAt)),
|
|
item => item.id,
|
|
)
|
|
this.cards = cards
|
|
this.updateGroups()
|
|
if (cards.length > 0) {
|
|
this.viewTime = lodash.last(cards).viewAt
|
|
}
|
|
this.hasMorePage = items.length !== 0
|
|
if (this.hasMorePage && this.groups.length === 0) {
|
|
await this.nextPage()
|
|
}
|
|
},
|
|
async updateHistoryPauseState() {
|
|
const result = await bilibiliApi(
|
|
getJsonWithCredentials('https://api.bilibili.com/x/v2/history/shadow'),
|
|
)
|
|
/*
|
|
result == true: 暂停
|
|
result == {}: 没暂停
|
|
*/
|
|
this.paused = result === true
|
|
},
|
|
async toggleHistoryPause() {
|
|
const targetState = !this.paused
|
|
try {
|
|
this.paused = targetState
|
|
await postTextWithCredentials(
|
|
'https://api.bilibili.com/x/v2/history/shadow/set',
|
|
formData({
|
|
csrf: getCsrf(),
|
|
switch: targetState,
|
|
}),
|
|
)
|
|
} catch (error) {
|
|
this.paused = !targetState
|
|
}
|
|
},
|
|
},
|
|
})
|
|
</script>
|
|
<style lang="scss">
|
|
@import 'common';
|
|
@import '../popup';
|
|
|
|
.custom-navbar-history-list {
|
|
width: 400px;
|
|
@include navbar-popup-height();
|
|
font-size: 12px;
|
|
padding: 0;
|
|
margin: 0;
|
|
@include v-stretch();
|
|
justify-content: center;
|
|
@mixin items-animation {
|
|
&-enter,
|
|
&-leave-to {
|
|
opacity: 0;
|
|
transform: translateY(-16px) scale(0.9);
|
|
}
|
|
&-leave-active {
|
|
transition: 0.24s cubic-bezier(0.22, 0.61, 0.36, 1);
|
|
position: absolute;
|
|
}
|
|
}
|
|
@mixin round-button($size: 26px) {
|
|
width: $size;
|
|
height: $size;
|
|
box-sizing: border-box;
|
|
}
|
|
.header {
|
|
@include v-stretch(6px);
|
|
margin: 16px 12px 4px 12px;
|
|
.header-row {
|
|
@include h-stretch(8px);
|
|
.row-title {
|
|
@include h-center();
|
|
}
|
|
}
|
|
.type-filters {
|
|
@include h-center(6px);
|
|
.type-filter {
|
|
.be-button {
|
|
padding: 4px 8px 4px 6px;
|
|
// color: #8888;
|
|
// .be-icon {
|
|
// margin-right: 6px;
|
|
// }
|
|
// &.checked {
|
|
// color: inherit;
|
|
// }
|
|
}
|
|
}
|
|
}
|
|
.search {
|
|
flex: 1;
|
|
.be-textbox {
|
|
height: 100%;
|
|
}
|
|
}
|
|
.operations {
|
|
@include h-center(8px);
|
|
.operation {
|
|
.be-button {
|
|
@include round-button();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.content {
|
|
@include v-stretch();
|
|
@include no-scrollbar();
|
|
justify-content: space-between;
|
|
flex-grow: 1;
|
|
.be-scroll-trigger,
|
|
.be-empty,
|
|
.be-loading {
|
|
align-self: center;
|
|
text-align: center;
|
|
margin: 12px 0;
|
|
}
|
|
.cards {
|
|
@include items-animation();
|
|
flex: 1;
|
|
scroll-behavior: smooth;
|
|
position: relative;
|
|
@include no-scrollbar();
|
|
padding-bottom: 12px;
|
|
.empty-tip {
|
|
text-align: center;
|
|
}
|
|
.time-group {
|
|
// padding-bottom: 16px;
|
|
@include items-animation();
|
|
&-name {
|
|
padding: 8px 12px;
|
|
font-size: 12px;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 1;
|
|
background-color: #fff;
|
|
body.dark & {
|
|
background-color: #222;
|
|
}
|
|
}
|
|
&-items {
|
|
padding: 0 12px;
|
|
.floating {
|
|
@include round-bar(20);
|
|
@include h-center();
|
|
background-color: #000c;
|
|
color: white;
|
|
justify-content: center;
|
|
position: absolute;
|
|
opacity: 0;
|
|
font-size: 11px;
|
|
}
|
|
.time-group-item {
|
|
display: grid;
|
|
grid-template:
|
|
'cover title title' 5fr
|
|
'cover up time' 6fr / 80px 1fr auto;
|
|
border-radius: 8px;
|
|
color: black;
|
|
background-color: #fff;
|
|
box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.05);
|
|
border: 1px solid #8882;
|
|
box-sizing: border-box;
|
|
body.dark & {
|
|
background-color: #282828;
|
|
color: #eee;
|
|
}
|
|
&:not(:last-child) {
|
|
margin-bottom: 8px;
|
|
}
|
|
&:hover {
|
|
.cover {
|
|
transform: scale(1.05);
|
|
}
|
|
.floating {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
.cover-container {
|
|
$height: 55px;
|
|
$padding: 2px;
|
|
grid-area: cover;
|
|
position: relative;
|
|
height: $height;
|
|
overflow: hidden;
|
|
border-radius: 7px 0 0 7px;
|
|
.cover {
|
|
object-fit: cover;
|
|
width: 80px;
|
|
height: $height;
|
|
body.dark &.placeholder {
|
|
filter: invert(0.9);
|
|
}
|
|
}
|
|
.duration {
|
|
left: $padding;
|
|
bottom: $padding;
|
|
padding: 0 6px;
|
|
}
|
|
.live-status {
|
|
&.on {
|
|
background-color: var(--theme-color);
|
|
color: var(--foreground-color);
|
|
}
|
|
}
|
|
.progress-number {
|
|
left: $padding;
|
|
top: $padding;
|
|
padding: 0 6px;
|
|
}
|
|
.progress {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
height: 2px;
|
|
border-radius: 1px;
|
|
background-color: var(--theme-color);
|
|
}
|
|
}
|
|
.title {
|
|
@include semi-bold();
|
|
grid-area: title;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
align-self: end;
|
|
margin: 0;
|
|
line-height: normal;
|
|
display: block;
|
|
padding-left: 8px;
|
|
padding-right: 6px;
|
|
font-size: 13px;
|
|
&:hover {
|
|
color: var(--theme-color) !important;
|
|
}
|
|
}
|
|
.up,
|
|
.time {
|
|
font-size: 11px;
|
|
opacity: 0.75;
|
|
align-self: center;
|
|
}
|
|
.up {
|
|
grid-area: up;
|
|
@include h-center();
|
|
padding-left: 8px;
|
|
opacity: 1;
|
|
.be-icon {
|
|
margin-right: 4px;
|
|
font-size: 14px;
|
|
}
|
|
&-face {
|
|
border-radius: 50%;
|
|
width: 18px;
|
|
height: 18px;
|
|
margin-right: 4px;
|
|
}
|
|
&-name {
|
|
white-space: nowrap;
|
|
max-width: 160px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
opacity: 0.75;
|
|
&:hover {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
}
|
|
.time {
|
|
font-size: 11px;
|
|
grid-area: time;
|
|
padding-right: 6px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|