mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-06-04 07:09:54 +08:00
feat: add subscription sort options
This commit is contained in:
@@ -995,6 +995,13 @@ export default {
|
||||
paused: 'Paused',
|
||||
cardStatePaused: 'Paused',
|
||||
cardStatePending: 'Pending',
|
||||
sortTitle: 'Sort',
|
||||
sort: {
|
||||
custom: 'Custom',
|
||||
lastUpdate: 'Last Updated',
|
||||
addTime: 'Added Time',
|
||||
lackEpisode: 'Missing Episodes',
|
||||
},
|
||||
selectedCount: 'Selected {count}/{total} items',
|
||||
noSelectedItems: 'Please select subscriptions to operate',
|
||||
batchEnable: 'Batch Enable',
|
||||
|
||||
@@ -990,6 +990,13 @@ export default {
|
||||
paused: '暂停',
|
||||
cardStatePaused: '已暂停',
|
||||
cardStatePending: '待定中',
|
||||
sortTitle: '排序',
|
||||
sort: {
|
||||
custom: '自定义',
|
||||
lastUpdate: '最后更新时间',
|
||||
addTime: '添加时间',
|
||||
lackEpisode: '缺失集数',
|
||||
},
|
||||
selectedCount: '已选择 {count}/{total} 项',
|
||||
noSelectedItems: '请先选择要操作的订阅',
|
||||
batchEnable: '批量启用',
|
||||
|
||||
@@ -990,6 +990,13 @@ export default {
|
||||
paused: '暫停',
|
||||
cardStatePaused: '已暫停',
|
||||
cardStatePending: '待定中',
|
||||
sortTitle: '排序',
|
||||
sort: {
|
||||
custom: '自定義',
|
||||
lastUpdate: '最後更新時間',
|
||||
addTime: '添加時間',
|
||||
lackEpisode: '缺失集數',
|
||||
},
|
||||
selectedCount: '已選擇 {count}/{total} 項',
|
||||
noSelectedItems: '請先選擇要操作的訂閱',
|
||||
batchEnable: '批量啟用',
|
||||
|
||||
@@ -54,6 +54,11 @@ const subscribeFilter = ref('')
|
||||
// 订阅状态筛选
|
||||
const subscribeStatusFilter = ref<string | null>(null)
|
||||
|
||||
type SubscribeSortBy = 'custom' | 'last_update' | 'date' | 'lack_episode'
|
||||
|
||||
// 订阅排序方式
|
||||
const subscribeSortBy = ref<SubscribeSortBy | ''>('')
|
||||
|
||||
// 分享搜索词
|
||||
const shareKeyword = ref('')
|
||||
const shareKeywordInput = ref('')
|
||||
@@ -85,6 +90,17 @@ const filterOptions = computed(() => {
|
||||
]
|
||||
})
|
||||
|
||||
// 排序选项
|
||||
const sortOptions = computed<Array<{ value: SubscribeSortBy; label: string }>>(() => [
|
||||
{ value: 'custom', label: t('subscribe.sort.custom') },
|
||||
{ value: 'last_update', label: t('subscribe.sort.lastUpdate') },
|
||||
{ value: 'date', label: t('subscribe.sort.addTime') },
|
||||
{ value: 'lack_episode', label: t('subscribe.sort.lackEpisode') },
|
||||
])
|
||||
|
||||
// 当前选中的排序选项
|
||||
const currentSortBy = computed<SubscribeSortBy>(() => subscribeSortBy.value || 'date')
|
||||
|
||||
// 当前选中的筛选选项
|
||||
const currentFilter = computed(() => {
|
||||
return filterOptions.value.find(option => option.value === (subscribeStatusFilter.value || 'all'))
|
||||
@@ -104,6 +120,14 @@ function selectFilter(value: string) {
|
||||
filterSubscribeDialog.value = false
|
||||
}
|
||||
|
||||
// 选择订阅排序选项,非自定义排序会退出拖拽排序模式。
|
||||
function selectSubscribeSort(value: SubscribeSortBy) {
|
||||
subscribeSortBy.value = value
|
||||
if (value !== 'custom') {
|
||||
subscribeSortMode.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// VMenu activator选择器
|
||||
const filterActivator = computed(() => '[data-menu-activator="filter-btn"]')
|
||||
const searchActivator = computed(() => '[data-menu-activator="share-filter-btn"]')
|
||||
@@ -132,7 +156,11 @@ function openShareStatisticsDialog() {
|
||||
openSharedDialog(SubscribeShareStatisticsDialog, {}, {}, { closeOn: ['close'] })
|
||||
}
|
||||
|
||||
// 切换订阅拖拽排序模式,进入时固定使用自定义排序。
|
||||
function toggleSubscribeSortMode() {
|
||||
if (!subscribeSortMode.value) {
|
||||
subscribeSortBy.value = 'custom'
|
||||
}
|
||||
subscribeSortMode.value = !subscribeSortMode.value
|
||||
}
|
||||
|
||||
@@ -290,8 +318,10 @@ onMounted(() => {
|
||||
:keyword="subscribeFilter"
|
||||
:status-filter="subscribeStatusFilter ?? ''"
|
||||
:sort-mode="subscribeSortMode"
|
||||
:sort-by="subscribeSortBy"
|
||||
:active="activeTab === 'mysub'"
|
||||
@update:sort-mode="subscribeSortMode = $event"
|
||||
@update:sort-by="subscribeSortBy = $event"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
@@ -358,6 +388,23 @@ onMounted(() => {
|
||||
</template>
|
||||
</VListItem>
|
||||
</VList>
|
||||
<VDivider />
|
||||
<!-- 排序 -->
|
||||
<VList density="compact" class="px-2 py-1">
|
||||
<VListSubheader>{{ t('subscribe.sortTitle') }}</VListSubheader>
|
||||
<VListItem
|
||||
v-for="option in sortOptions"
|
||||
:key="option.value"
|
||||
:active="currentSortBy === option.value"
|
||||
@click="selectSubscribeSort(option.value)"
|
||||
density="compact"
|
||||
>
|
||||
<VListItemTitle>{{ option.label }}</VListItemTitle>
|
||||
<template #append>
|
||||
<VIcon v-if="currentSortBy === option.value" icon="mdi-check" color="primary" size="small" />
|
||||
</template>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VCard>
|
||||
</VMenu>
|
||||
</Teleport>
|
||||
|
||||
@@ -40,6 +40,10 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
sortBy: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
@@ -48,8 +52,11 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:sortMode': [value: boolean]
|
||||
'update:sortBy': [value: SubscribeSortBy]
|
||||
}>()
|
||||
|
||||
type SubscribeSortBy = 'custom' | 'last_update' | 'date' | 'lack_episode'
|
||||
|
||||
// 是否刷新过
|
||||
let isRefreshed = ref(false)
|
||||
|
||||
@@ -71,8 +78,16 @@ const selectedSubscribes = ref<number[]>([])
|
||||
|
||||
const normalizedKeyword = computed(() => props.keyword?.trim().toLowerCase() || '')
|
||||
const selectedSubscribesSet = computed(() => new Set(selectedSubscribes.value))
|
||||
const hasCustomOrder = computed(() => orderConfig.value.length > 0)
|
||||
const effectiveSortBy = computed<SubscribeSortBy>(() => {
|
||||
return (props.sortBy as SubscribeSortBy) || (hasCustomOrder.value ? 'custom' : 'date')
|
||||
})
|
||||
const canSortContext = computed(
|
||||
() => !normalizedKeyword.value && (!props.statusFilter || props.statusFilter === 'all') && !isBatchMode.value,
|
||||
() =>
|
||||
effectiveSortBy.value === 'custom' &&
|
||||
!normalizedKeyword.value &&
|
||||
(!props.statusFilter || props.statusFilter === 'all') &&
|
||||
!isBatchMode.value,
|
||||
)
|
||||
const sortMode = computed({
|
||||
get: () => props.sortMode,
|
||||
@@ -130,11 +145,62 @@ function getSubscribeStatus(subscribe: Subscribe) {
|
||||
// API请求键值(计算属性)
|
||||
const orderRequestKey = computed(() => (props.type === '电影' ? 'SubscribeMovieOrder' : 'SubscribeTvOrder'))
|
||||
|
||||
// 监听数据和筛选变化,同步更新显示列表
|
||||
// 转换订阅时间字段为可排序时间戳。
|
||||
function getSubscribeTimeValue(value?: string) {
|
||||
if (!value) return 0
|
||||
|
||||
const directTime = Date.parse(value)
|
||||
if (!Number.isNaN(directTime)) return directTime
|
||||
|
||||
const compatibleTime = Date.parse(value.replace(/-/g, '/'))
|
||||
return Number.isNaN(compatibleTime) ? 0 : compatibleTime
|
||||
}
|
||||
|
||||
// 按自定义顺序排序订阅,未配置顺序的订阅按添加时间倒序补齐。
|
||||
function sortByCustomOrder(a: Subscribe, b: Subscribe, orderIndexMap: Map<number, number>) {
|
||||
const aIndex = orderIndexMap.get(a.id) ?? Number.MAX_SAFE_INTEGER
|
||||
const bIndex = orderIndexMap.get(b.id) ?? Number.MAX_SAFE_INTEGER
|
||||
|
||||
if (aIndex !== bIndex) {
|
||||
return aIndex - bIndex
|
||||
}
|
||||
|
||||
return getSubscribeTimeValue(b.date) - getSubscribeTimeValue(a.date)
|
||||
}
|
||||
|
||||
// 按当前排序选项调整订阅列表顺序。
|
||||
function sortSubscribeList(list: Subscribe[]) {
|
||||
const orderIndexMap = new Map(orderConfig.value.map((item, index) => [item.id, index]))
|
||||
|
||||
list.sort((a, b) => {
|
||||
if (effectiveSortBy.value === 'custom') {
|
||||
return sortByCustomOrder(a, b, orderIndexMap)
|
||||
}
|
||||
|
||||
if (effectiveSortBy.value === 'last_update') {
|
||||
return getSubscribeTimeValue(b.last_update) - getSubscribeTimeValue(a.last_update)
|
||||
}
|
||||
|
||||
if (effectiveSortBy.value === 'lack_episode') {
|
||||
const lackEpisodeDiff = (b.lack_episode || 0) - (a.lack_episode || 0)
|
||||
return lackEpisodeDiff || getSubscribeTimeValue(b.date) - getSubscribeTimeValue(a.date)
|
||||
}
|
||||
|
||||
return getSubscribeTimeValue(b.date) - getSubscribeTimeValue(a.date)
|
||||
})
|
||||
}
|
||||
|
||||
// 同步订阅排序默认值给父组件。
|
||||
function syncDefaultSortBy() {
|
||||
if (!props.sortBy) {
|
||||
emit('update:sortBy', hasCustomOrder.value ? 'custom' : 'date')
|
||||
}
|
||||
}
|
||||
|
||||
// 监听数据、筛选和排序变化,同步更新显示列表
|
||||
watch(
|
||||
[dataList, normalizedKeyword, () => props.statusFilter, orderConfig],
|
||||
[dataList, normalizedKeyword, () => props.statusFilter, orderConfig, effectiveSortBy],
|
||||
() => {
|
||||
const orderIndexMap = new Map(orderConfig.value.map((item, index) => [item.id, index]))
|
||||
const nextDisplayList = dataList.value.filter(data => {
|
||||
if (data.type !== props.type) {
|
||||
return false
|
||||
@@ -155,12 +221,7 @@ watch(
|
||||
return true
|
||||
})
|
||||
|
||||
nextDisplayList.sort((a, b) => {
|
||||
const aIndex = orderIndexMap.get(a.id) ?? Number.MAX_SAFE_INTEGER
|
||||
const bIndex = orderIndexMap.get(b.id) ?? Number.MAX_SAFE_INTEGER
|
||||
|
||||
return aIndex - bIndex
|
||||
})
|
||||
sortSubscribeList(nextDisplayList)
|
||||
|
||||
displayList.value = nextDisplayList
|
||||
},
|
||||
@@ -184,9 +245,11 @@ async function loadSubscribeOrderConfig() {
|
||||
if (response && response.data && response.data.value) {
|
||||
orderConfig.value = response.data.value
|
||||
}
|
||||
syncDefaultSortBy()
|
||||
} catch (error) {
|
||||
console.error('Failed to load subscribe order config:', error)
|
||||
orderConfig.value = []
|
||||
syncDefaultSortBy()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,6 +258,7 @@ async function saveSubscribeOrder() {
|
||||
// 顺序配置
|
||||
const orderObj = displayList.value.map(item => ({ id: item.id }))
|
||||
orderConfig.value = orderObj
|
||||
emit('update:sortBy', 'custom')
|
||||
|
||||
// 保存到服务端
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user