Refactor navbar history filters (fix #3676, #2729)

This commit is contained in:
the1812 2022-12-10 22:10:56 +08:00
parent c658cd67e5
commit 9dc2ad58f4
10 changed files with 90 additions and 47 deletions

View File

@ -155,6 +155,8 @@ export default Vue.extend({
</script> </script>
<style lang="scss"> <style lang="scss">
@import 'common';
.custom-navbar-item { .custom-navbar-item {
color: inherit; color: inherit;
position: relative; position: relative;
@ -216,7 +218,7 @@ export default Vue.extend({
} }
&.active .main-content { &.active .main-content {
font-weight: bold; @include semi-bold();
font-size: 11pt; font-size: 11pt;
} }

View File

@ -409,7 +409,7 @@ export default Vue.extend({
.title { .title {
grid-area: title; grid-area: title;
font-size: 13px; font-size: 13px;
font-weight: bold; @include semi-bold();
@include max-line(2); @include max-line(2);
-webkit-box-align: start; -webkit-box-align: start;
margin: 0; margin: 0;

View File

@ -25,15 +25,15 @@
<div class="row-title">过滤:</div> <div class="row-title">过滤:</div>
<div class="type-filters"> <div class="type-filters">
<div v-for="t of types" :key="t.name" class="type-filter"> <div v-for="t of types" :key="t.name" class="type-filter">
<VButton <RadioButton
round
:title="(t.checked ? '不显示' : '显示') + t.displayName" :title="(t.checked ? '不显示' : '显示') + t.displayName"
:class="{ checked: t.checked }" :class="{ checked: t.checked }"
@click="toggleTypeFilter(t)" :checked="t.checked"
:disabled="loading"
@change="toggleTypeFilter(t)"
> >
<VIcon :icon="t.icon" :size="18"></VIcon>
{{ t.displayName }} {{ t.displayName }}
</VButton> </RadioButton>
</div> </div>
</div> </div>
</div> </div>
@ -108,14 +108,24 @@
import { bilibiliApi, getJsonWithCredentials, postTextWithCredentials } from '@/core/ajax' import { bilibiliApi, getJsonWithCredentials, postTextWithCredentials } from '@/core/ajax'
import { formData, getCsrf } from '@/core/utils' import { formData, getCsrf } from '@/core/utils'
import { descendingSort } from '@/core/utils/sort' import { descendingSort } from '@/core/utils/sort'
import { VButton, VIcon, TextBox, VLoading, VEmpty, ScrollTrigger, DpiImage } from '@/ui' import {
VButton,
VIcon,
RadioButton,
TextBox,
VLoading,
VEmpty,
ScrollTrigger,
DpiImage,
} from '@/ui'
import { popperMixin } from '../mixins' import { popperMixin } from '../mixins'
import { types, TypeFilter, HistoryItem, getHistoryItems, group } from './types' import { types, TypeFilter, HistoryItem, getHistoryItems, group, HistoryType } from './types'
export default Vue.extend({ export default Vue.extend({
components: { components: {
VButton, VButton,
VIcon, VIcon,
RadioButton,
TextBox, TextBox,
VLoading, VLoading,
VEmpty, VEmpty,
@ -137,17 +147,12 @@ export default Vue.extend({
}, },
computed: { computed: {
canNextPage() { canNextPage() {
return ( return this.search === '' && !this.loading && this.hasMorePage
this.search === '' &&
!this.loading &&
this.hasMorePage &&
this.types.every((t: TypeFilter) => t.checked)
)
}, },
}, },
watch: { watch: {
search: lodash.debounce(function search() { search: lodash.debounce(function search() {
this.updateGroups() this.reloadHistoryItems()
}, 200), }, 200),
}, },
async created() { async created() {
@ -159,11 +164,22 @@ export default Vue.extend({
}, },
methods: { methods: {
toggleTypeFilter(typeFilter: TypeFilter) { toggleTypeFilter(typeFilter: TypeFilter) {
typeFilter.checked = !typeFilter.checked types.forEach(t => (t.checked = t.name === typeFilter.name))
this.updateGroups() this.reloadHistoryItems()
},
async reloadHistoryItems() {
this.viewTime = 0
this.hasMorePage = true
this.loading = true
try {
await this.nextPage()
} finally {
this.loading = false
}
}, },
filterFunc(item: HistoryItem) { filterFunc(item: HistoryItem) {
if (types.some(t => t.name === item.type && !t.checked)) { const isAllType = types.find(it => it.name === HistoryType.All).checked
if (!isAllType && types.some(t => t.name === item.type && !t.checked)) {
return false return false
} }
if ( if (
@ -178,7 +194,10 @@ export default Vue.extend({
this.groups = group(this.cards.filter(this.filterFunc)) this.groups = group(this.cards.filter(this.filterFunc))
}, },
async nextPage() { async nextPage() {
const items = await getHistoryItems(this.viewTime) const items = await getHistoryItems(
this.viewTime,
types.find(t => t.checked),
)
const cards: HistoryItem[] = lodash.uniqBy( const cards: HistoryItem[] = lodash.uniqBy(
this.cards.concat(items).sort(descendingSort((item: HistoryItem) => item.viewAt)), this.cards.concat(items).sort(descendingSort((item: HistoryItem) => item.viewAt)),
item => item.id, item => item.id,
@ -188,7 +207,10 @@ export default Vue.extend({
if (cards.length > 0) { if (cards.length > 0) {
this.viewTime = lodash.last(cards).viewAt this.viewTime = lodash.last(cards).viewAt
} }
this.hasMorePage = cards.length !== 0 this.hasMorePage = items.length !== 0
if (this.hasMorePage && this.groups.length === 0) {
await this.nextPage()
}
}, },
async updateHistoryPauseState() { async updateHistoryPauseState() {
const result = await bilibiliApi( const result = await bilibiliApi(
@ -223,7 +245,7 @@ export default Vue.extend({
@import '../popup'; @import '../popup';
.custom-navbar-history-list { .custom-navbar-history-list {
width: 350px; width: 400px;
@include navbar-popup-height(); @include navbar-popup-height();
font-size: 12px; font-size: 12px;
padding: 0; padding: 0;
@ -251,23 +273,22 @@ export default Vue.extend({
margin: 16px 12px 4px 12px; margin: 16px 12px 4px 12px;
.header-row { .header-row {
@include h-stretch(8px); @include h-stretch(8px);
justify-content: space-between;
.row-title { .row-title {
@include h-center(); @include h-center();
} }
} }
.type-filters { .type-filters {
@include h-center(8px); @include h-center(6px);
.type-filter { .type-filter {
.be-button { .be-button {
padding: 4px 8px 4px 6px; padding: 4px 8px 4px 6px;
color: #8888; // color: #8888;
.be-icon { // .be-icon {
margin-right: 6px; // margin-right: 6px;
} // }
&.checked { // &.checked {
color: inherit; // color: inherit;
} // }
} }
} }
} }
@ -402,8 +423,8 @@ export default Vue.extend({
} }
} }
.title { .title {
@include semi-bold();
grid-area: title; grid-area: title;
font-weight: bold;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;

View File

@ -11,7 +11,7 @@ export const history: CustomNavbarItemInit = {
active: document.URL.replace(/\?.*$/, '') === href, active: document.URL.replace(/\?.*$/, '') === href,
loginRequired: true, loginRequired: true,
boundingWidth: 350, boundingWidth: 400,
noPopupPadding: true, noPopupPadding: true,
popupContent: () => import('./NavbarHistory.vue').then(m => m.default), popupContent: () => import('./NavbarHistory.vue').then(m => m.default),
} }

View File

@ -4,6 +4,7 @@ import { formatDuration } from '@/core/utils/formatters'
/** 历史项目类型, 值为 API 中的 `history.business` */ /** 历史项目类型, 值为 API 中的 `history.business` */
export enum HistoryType { export enum HistoryType {
All = 'all',
Video = 'archive', Video = 'archive',
Live = 'live', Live = 'live',
Article = 'article', Article = 'article',
@ -48,32 +49,44 @@ export interface TypeFilter {
displayName: string displayName: string
icon: string icon: string
checked: boolean checked: boolean
apiType: string
} }
/** 所有的历史记录类型 */ /** 所有的历史记录类型 */
export const types = [ export const types = [
{
name: HistoryType.All,
displayName: '全部',
icon: '',
checked: true,
apiType: '',
},
{ {
name: HistoryType.Video, name: HistoryType.Video,
displayName: '视频', displayName: '视频',
icon: 'mdi-play-circle-outline', icon: 'mdi-play-circle-outline',
checked: true, checked: false,
apiType: 'archive',
}, },
{ {
name: HistoryType.Bangumi, name: HistoryType.Bangumi,
displayName: '番剧', displayName: '番剧',
icon: 'mdi-television-classic', icon: 'mdi-television-classic',
checked: true, checked: false,
apiType: 'archive',
}, },
{ {
name: HistoryType.Live, name: HistoryType.Live,
displayName: '直播', displayName: '直播',
icon: 'mdi-video-wireless-outline', icon: 'mdi-video-wireless-outline',
checked: true, checked: false,
apiType: 'live',
}, },
{ {
name: HistoryType.Article, name: HistoryType.Article,
displayName: '专栏', displayName: '专栏',
icon: 'mdi-newspaper-variant-outline', icon: 'mdi-newspaper-variant-outline',
checked: true, checked: false,
apiType: 'article',
}, },
] as TypeFilter[] ] as TypeFilter[]
@ -188,12 +201,17 @@ const parseHistoryItem = (item: any): HistoryItem => {
* , * ,
* @param viewTime * @param viewTime
*/ */
export const getHistoryItems = async (viewTime?: number) => { export const getHistoryItems = async (viewTime?: number, type?: TypeFilter) => {
let api = 'https://api.bilibili.com/x/web-interface/history/cursor' const api = 'https://api.bilibili.com/x/web-interface/history/cursor'
const params = new URLSearchParams()
if (viewTime) { if (viewTime) {
api += `?view_at=${Math.round(viewTime / 1000)}` params.set('view_at', Math.round(viewTime / 1000).toString())
} }
const { list } = await bilibiliApi(getJsonWithCredentials(api), '获取历史记录失败') params.set('type', type?.apiType ?? '')
const { list } = await bilibiliApi(
getJsonWithCredentials(`${api}?${params.toString()}`),
'获取历史记录失败',
)
if (!Array.isArray(list)) { if (!Array.isArray(list)) {
return [] return []
} }

View File

@ -186,7 +186,7 @@ export default Vue.extend({
.title { .title {
margin-left: 6px; margin-left: 6px;
font-size: 18px; font-size: 18px;
font-weight: bold; @include semi-bold();
} }
.grow { .grow {
flex: 1; flex: 1;

View File

@ -233,7 +233,7 @@ export default Vue.extend({
} }
.title { .title {
font-size: 14px; font-size: 14px;
font-weight: bold; @include semi-bold();
padding-top: 4px; padding-top: 4px;
color: inherit; color: inherit;
white-space: nowrap; white-space: nowrap;

View File

@ -325,6 +325,8 @@ export default Vue.extend({
</script> </script>
<style lang="scss"> <style lang="scss">
@import 'common';
.user-info-panel { .user-info-panel {
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
@ -441,7 +443,7 @@ export default Vue.extend({
.welcome, .welcome,
.name { .name {
font-size: 16px; font-size: 16px;
font-weight: bold; @include semi-bold();
margin: 46px 0 16px 0; margin: 46px 0 16px 0;
text-align: center; text-align: center;
color: inherit; color: inherit;
@ -495,7 +497,7 @@ export default Vue.extend({
color: var(--theme-color) !important; color: var(--theme-color) !important;
} }
.stats-number { .stats-number {
font-weight: bold; @include semi-bold();
margin-bottom: 4px; margin-bottom: 4px;
font-size: 14px; font-size: 14px;
transition: none; transition: none;

View File

@ -326,7 +326,7 @@ export default Vue.extend({
.title { .title {
grid-area: title; grid-area: title;
font-size: 13px; font-size: 13px;
font-weight: bold; @include semi-bold();
margin: 0; margin: 0;
margin-top: 8px; margin-top: 8px;
padding: 0 10px; padding: 0 10px;

View File

@ -91,7 +91,7 @@ export default Vue.extend({
&.left-icon { &.left-icon {
.icon-container { .icon-container {
order: -1; order: -1;
margin: 2px 8px 2px 0; margin: 2px 6px 2px 0;
} }
} }
} }