mirror of
https://github.com/Cp0204/quark-auto-save.git
synced 2026-01-15 00:40:43 +08:00
在任务列表页面新增排序功能
This commit is contained in:
parent
591c9e9fe1
commit
d8749ff69f
@ -7743,6 +7743,167 @@ div:has(> .collapse:not(.show)):has(+ .row.title[title^="资源搜索"]) {
|
|||||||
|
|
||||||
/* 任务列表:类型筛选按钮与上方名称筛选区域的间距 */
|
/* 任务列表:类型筛选按钮与上方名称筛选区域的间距 */
|
||||||
.tasklist-type-filter {
|
.tasklist-type-filter {
|
||||||
margin-top: 8px;
|
margin-top: 0px; /* 上移8px(从8px调整为0px) */
|
||||||
margin-bottom: 20px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 排序模块内部的行容器,承载三段控件 */
|
||||||
|
.tasklist-sort-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* spacer:撑开空间以将控件推至右侧 */
|
||||||
|
.tasklist-sort-spacer {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 三段胶囊通用样式(灰底、统一高度/字号) */
|
||||||
|
.tasklist-sort-pill {
|
||||||
|
background-color: var(--button-gray-background-color) !important;
|
||||||
|
color: var(--dark-text-color);
|
||||||
|
border: none !important;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
margin: 0 !important;
|
||||||
|
font-size: 0.95rem; /* 与左侧类型按钮一致 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左侧“按”按钮:保留边框,去右边框,与中间边框重叠 */
|
||||||
|
.tasklist-sort-pill-icon {
|
||||||
|
width: 31px;
|
||||||
|
min-width: 31px;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1px solid var(--border-color) !important;
|
||||||
|
border-right: none !important;
|
||||||
|
border-radius: 6px 0 0 6px;
|
||||||
|
background-color: var(--button-gray-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 中间下拉:白底,负责显示唯一可见边框(上下左右均有) */
|
||||||
|
.tasklist-sort-select {
|
||||||
|
padding: 0 8px;
|
||||||
|
border: 1px solid var(--border-color) !important;
|
||||||
|
border-radius: 0;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
background: #fff !important;
|
||||||
|
cursor: pointer;
|
||||||
|
background-image: none;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 30px;
|
||||||
|
padding-top: 0 !important;
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.95rem; /* 与左侧类型按钮一致 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 隐藏 IE 默认下拉箭头 */
|
||||||
|
.tasklist-sort-select::-ms-expand {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 聚焦高亮,保持与任务筛选一致 */
|
||||||
|
.tasklist-sort-select:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border-color: var(--focus-border-color) !important; /* 聚焦时四边统一为主题色 */
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 下拉聚焦时,右侧按钮与其接缝的左边框采用同色,视觉连贯 */
|
||||||
|
.tasklist-sort-select:focus + .tasklist-sort-order {
|
||||||
|
border-left-color: var(--focus-border-color) !important; /* 接缝同色,视觉连贯 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧"升/降序排列"按钮:保留边框,去左边框,与中间重叠 */
|
||||||
|
.tasklist-sort-order {
|
||||||
|
padding: 0 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid var(--border-color) !important;
|
||||||
|
border-left: none !important;
|
||||||
|
border-radius: 0 6px 6px 0;
|
||||||
|
background: var(--button-gray-background-color) !important;
|
||||||
|
font-size: 0.95rem; /* 与左侧类型按钮一致 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 兼容旧结构:若存在下拉菜单 DOM,最小宽度不小于 160px */
|
||||||
|
.tasklist-sort-dropdown .dropdown-menu {
|
||||||
|
min-width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 任务列表:移动端样式优化(复用追剧日历样式) */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
/* 任务列表:顶部一行(类型筛选 + 排序)不换行 */
|
||||||
|
.tasklist-header-row {
|
||||||
|
flex-wrap: nowrap !important;
|
||||||
|
align-items: center;
|
||||||
|
overflow-x: auto !important; /* 整行作为滚动容器 */
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scrollbar-width: none;
|
||||||
|
column-gap: 8px; /* 左右两组之间 8px 间距 */
|
||||||
|
margin-top: 0px !important;
|
||||||
|
/* 去除内边距,使用遮罩实现两侧 20px 的"硬遮挡"效果(无渐变) */
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
-webkit-mask-image: linear-gradient(to right,
|
||||||
|
transparent 0,
|
||||||
|
transparent 15px,
|
||||||
|
black 15px,
|
||||||
|
black calc(100% - 15px),
|
||||||
|
transparent calc(100% - 15px),
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
mask-image: linear-gradient(to right,
|
||||||
|
transparent 0,
|
||||||
|
transparent 15px,
|
||||||
|
black 15px,
|
||||||
|
black calc(100% - 15px),
|
||||||
|
transparent calc(100% - 15px),
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
.tasklist-header-row::-webkit-scrollbar { display: none; }
|
||||||
|
|
||||||
|
/* 两列都按内容宽度,不允许被压缩到0,通过外层行滚动 */
|
||||||
|
.tasklist-header-row > .col-lg-8.col-md-6,
|
||||||
|
.tasklist-header-row > .col-lg-4.col-md-6 {
|
||||||
|
flex: 0 0 auto !important;
|
||||||
|
width: auto !important;
|
||||||
|
max-width: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 去除列内侧补白,避免视觉间距异常 */
|
||||||
|
.tasklist-header-row > .col-lg-8.col-md-6 { padding-right: 0 !important; }
|
||||||
|
.tasklist-header-row > .col-lg-4.col-md-6 { padding-left: 0 !important; }
|
||||||
|
|
||||||
|
/* 左侧类型筛选按钮容器:保持单行(滚动交给外层行)*/
|
||||||
|
.tasklist-header-row .calendar-category-buttons {
|
||||||
|
flex-wrap: nowrap !important;
|
||||||
|
margin-top: 0 !important; /* 覆盖全局 -12px,避免被遮挡 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧排序组件容器:强制单行,超出部分横向滚动,隐藏滚动条 */
|
||||||
|
.tasklist-sort-controls {
|
||||||
|
width: auto !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
flex-wrap: nowrap !important;
|
||||||
|
overflow-x: auto !important;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scrollbar-width: none;
|
||||||
|
margin-top: -4px !important;
|
||||||
|
}
|
||||||
|
.tasklist-sort-controls::-webkit-scrollbar { display: none; }
|
||||||
|
|
||||||
|
/* 排序组件内部容器:保持紧凑布局 */
|
||||||
|
.tasklist-sort-controls .tasklist-sort-wrapper {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -949,30 +949,52 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 任务列表:类型筛选按钮(复用追剧日历样式) -->
|
<!-- 任务列表:类型筛选按钮和排序组件(复用追剧日历移动端样式) -->
|
||||||
<div class="calendar-category-buttons tasklist-type-filter">
|
<div class="row mb-3 tasklist-header-row">
|
||||||
<button type="button"
|
<div class="col-lg-8 col-md-6">
|
||||||
class="btn btn-outline-secondary calendar-category-btn"
|
<!-- 类型筛选按钮 -->
|
||||||
:class="{ active: tasklist.selectedType === 'all' }"
|
<div class="calendar-category-buttons tasklist-type-filter">
|
||||||
@click="selectTasklistType('all')">
|
<button type="button"
|
||||||
全部
|
class="btn btn-outline-secondary calendar-category-btn"
|
||||||
</button>
|
:class="{ active: tasklist.selectedType === 'all' }"
|
||||||
<button type="button"
|
@click="selectTasklistType('all')">
|
||||||
class="btn btn-outline-secondary calendar-category-btn"
|
全部
|
||||||
v-for="type in tasklist.contentTypes"
|
</button>
|
||||||
:key="'tasklist-'+type"
|
<button type="button"
|
||||||
:class="{ active: tasklist.selectedType === type }"
|
class="btn btn-outline-secondary calendar-category-btn"
|
||||||
@click="selectTasklistType(type)">
|
v-for="type in tasklist.contentTypes"
|
||||||
{{ getContentTypeDisplayName(type) }}
|
:key="'tasklist-'+type"
|
||||||
</button>
|
:class="{ active: tasklist.selectedType === type }"
|
||||||
|
@click="selectTasklistType(type)">
|
||||||
|
{{ getContentTypeDisplayName(type) }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4 col-md-6">
|
||||||
|
<!-- 排序组件 -->
|
||||||
|
<div class="tasklist-sort-controls d-flex justify-content-end">
|
||||||
|
<div class="tasklist-sort-wrapper">
|
||||||
|
<span class="tasklist-sort-pill tasklist-sort-pill-icon" title="切换排序">按</span>
|
||||||
|
<select class="tasklist-sort-pill tasklist-sort-select task-filter-select" :value="tasklistSort.by" @change="changeTasklistSortBy($event.target.value)" title="选择排序方式">
|
||||||
|
<option value="index">任务编号</option>
|
||||||
|
<option value="name">任务名称</option>
|
||||||
|
<option v-if="formData.tmdb_api_key" value="progress">任务进度</option>
|
||||||
|
<option value="update_time">更新时间</option>
|
||||||
|
</select>
|
||||||
|
<span class="tasklist-sort-pill tasklist-sort-order" @click="toggleTasklistSortOrder" title="切换升降序">
|
||||||
|
{{ tasklistSort.order === 'asc' ? '升序排列' : '降序排列' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="(task, index) in formData.tasklist" :key="index" class="task mb-3">
|
<div v-for="(task, index) in sortedTasklist" :key="index" class="task mb-3" v-show="shouldShowTasklist">
|
||||||
<template v-if="(taskDirSelected == '' || task.taskname == taskDirSelected) && task.taskname.includes(taskNameFilter) && tasklistFilterByType(task)">
|
<template v-if="(taskDirSelected == '' || task.taskname == taskDirSelected) && task.taskname.includes(taskNameFilter) && tasklistFilterByType(task)">
|
||||||
<hr>
|
<hr>
|
||||||
<div class="form-group row" style="align-items:center">
|
<div class="form-group row" style="align-items:center">
|
||||||
<div class="col pl-0" data-toggle="collapse" :data-target="'#collapse_'+index" aria-expanded="true" :aria-controls="'collapse_'+index">
|
<div class="col pl-0" data-toggle="collapse" :data-target="'#collapse_'+index" aria-expanded="true" :aria-controls="'collapse_'+index">
|
||||||
<div class="btn btn-block text-left">
|
<div class="btn btn-block text-left">
|
||||||
<i class="bi bi-caret-right-fill"></i> #<span v-html="`${String(index+1).padStart(2, '0')} ${task.taskname}`"></span>
|
<i class="bi bi-caret-right-fill"></i> #<span v-html="`${String((task.__originalIndex !== undefined ? task.__originalIndex : index) + 1).padStart(2, '0')} ${task.taskname}`"></span>
|
||||||
<template v-for="key in formData.button_display_order">
|
<template v-for="key in formData.button_display_order">
|
||||||
<span v-if="key==='latest_transfer_file' && formData.button_display.latest_transfer_file !== 'disabled' && taskLatestFiles[task.taskname]"
|
<span v-if="key==='latest_transfer_file' && formData.button_display.latest_transfer_file !== 'disabled' && taskLatestFiles[task.taskname]"
|
||||||
class="task-latest-file"
|
class="task-latest-file"
|
||||||
@ -2969,6 +2991,18 @@
|
|||||||
selectedType: (localStorage.getItem('tasklist_selected_type') || 'all'),
|
selectedType: (localStorage.getItem('tasklist_selected_type') || 'all'),
|
||||||
contentTypes: []
|
contentTypes: []
|
||||||
},
|
},
|
||||||
|
// 任务列表排序设置(记忆到localStorage)
|
||||||
|
tasklistSort: (() => {
|
||||||
|
try {
|
||||||
|
const saved = JSON.parse(localStorage.getItem('tasklist_sort_options') || '{}');
|
||||||
|
let by = ['index','name','progress','update_time'].includes(saved.by) ? saved.by : 'index';
|
||||||
|
const order = (saved.order === 'asc' || saved.order === 'desc') ? saved.order : 'asc';
|
||||||
|
// 注意:这里无法检查 TMDB API 配置,会在 mounted 中处理
|
||||||
|
return { by, order };
|
||||||
|
} catch (e) {
|
||||||
|
return { by: 'index', order: 'asc' };
|
||||||
|
}
|
||||||
|
})(),
|
||||||
// 日历页面resize监听器
|
// 日历页面resize监听器
|
||||||
calendarResizeHandler: null,
|
calendarResizeHandler: null,
|
||||||
// 日历自动检测更新相关
|
// 日历自动检测更新相关
|
||||||
@ -3054,6 +3088,35 @@
|
|||||||
return list;
|
return list;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 任务列表:带排序的视图数据(使用计算属性,不修改原始数据)
|
||||||
|
sortedTasklist() {
|
||||||
|
return this.getSortedTasklist();
|
||||||
|
},
|
||||||
|
|
||||||
|
// 判断是否应该显示任务列表(避免排序闪烁)
|
||||||
|
shouldShowTasklist() {
|
||||||
|
const { by } = this.tasklistSort || { by: 'index' };
|
||||||
|
|
||||||
|
// 任务编号和任务名称排序不需要额外数据,可以直接显示
|
||||||
|
if (by === 'index' || by === 'name') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务进度排序需要 taskLatestFiles 和 calendar.tasks 数据
|
||||||
|
if (by === 'progress') {
|
||||||
|
return this.taskLatestFiles && Object.keys(this.taskLatestFiles).length > 0 &&
|
||||||
|
this.calendar.tasks && Array.isArray(this.calendar.tasks) && this.calendar.tasks.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新时间排序需要 taskLatestRecords 数据
|
||||||
|
if (by === 'update_time') {
|
||||||
|
return this.taskLatestRecords && Object.keys(this.taskLatestRecords).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认显示
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
// 海报和日历视图下过滤掉所有内容都是未匹配的分类
|
// 海报和日历视图下过滤掉所有内容都是未匹配的分类
|
||||||
filteredContentTypes() {
|
filteredContentTypes() {
|
||||||
if (!this.calendar.tasks || this.calendar.tasks.length === 0) {
|
if (!this.calendar.tasks || this.calendar.tasks.length === 0) {
|
||||||
@ -3519,6 +3582,79 @@
|
|||||||
this.stopCalendarAutoWatch();
|
this.stopCalendarAutoWatch();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// 任务列表排序:获取显示名称
|
||||||
|
getTasklistSortDisplayName(key) {
|
||||||
|
if (key === 'name') return '任务名称';
|
||||||
|
if (key === 'progress') return '任务进度';
|
||||||
|
if (key === 'update_time') return '更新时间';
|
||||||
|
return '任务编号';
|
||||||
|
},
|
||||||
|
// 任务列表排序:保存到localStorage
|
||||||
|
saveTasklistSort() {
|
||||||
|
try {
|
||||||
|
localStorage.setItem('tasklist_sort_options', JSON.stringify({ by: this.tasklistSort.by, order: this.tasklistSort.order }));
|
||||||
|
} catch (e) {}
|
||||||
|
},
|
||||||
|
// 任务列表排序:切换排序字段
|
||||||
|
// 仅切换排序字段,不改变当前升降序
|
||||||
|
changeTasklistSortBy(by) {
|
||||||
|
if (!['index','name','progress','update_time'].includes(by)) return;
|
||||||
|
if (this.tasklistSort.by !== by) {
|
||||||
|
this.tasklistSort.by = by;
|
||||||
|
// 不重置、不切换 order,保持现状
|
||||||
|
this.saveTasklistSort();
|
||||||
|
// 不再调用 applyTasklistSort,因为现在使用计算属性
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 任务列表排序:切换升降序
|
||||||
|
toggleTasklistSortOrder() {
|
||||||
|
this.tasklistSort.order = this.tasklistSort.order === 'asc' ? 'desc' : 'asc';
|
||||||
|
this.saveTasklistSort();
|
||||||
|
// 不再调用 applyTasklistSort,因为现在使用计算属性
|
||||||
|
},
|
||||||
|
// 获取排序后的任务列表(不修改原始数据,避免触发 configModified)
|
||||||
|
getSortedTasklist() {
|
||||||
|
try {
|
||||||
|
const { by, order } = this.tasklistSort || { by: 'index', order: 'asc' };
|
||||||
|
const factor = order === 'desc' ? -1 : 1;
|
||||||
|
// 建立稳定排序:附带原始索引,避免相等时抖动
|
||||||
|
const withIndex = (this.formData.tasklist || []).map((t, idx) => ({ t, idx }));
|
||||||
|
withIndex.sort((a, b) => {
|
||||||
|
let cmp = 0;
|
||||||
|
if (by === 'name') {
|
||||||
|
const an = (a.t.taskname || '').toString();
|
||||||
|
const bn = (b.t.taskname || '').toString();
|
||||||
|
try {
|
||||||
|
const ak = pinyinPro.pinyin(an, { toneType: 'none', type: 'string' }).toLowerCase();
|
||||||
|
const bk = pinyinPro.pinyin(bn, { toneType: 'none', type: 'string' }).toLowerCase();
|
||||||
|
if (ak < bk) cmp = -1; else if (ak > bk) cmp = 1; else cmp = 0;
|
||||||
|
} catch (e) { cmp = an.localeCompare(bn); }
|
||||||
|
} else if (by === 'progress') {
|
||||||
|
const ap = (this.getTaskProgress && this.getTaskProgress(a.t.taskname) != null) ? Number(this.getTaskProgress(a.t.taskname)) : -1;
|
||||||
|
const bp = (this.getTaskProgress && this.getTaskProgress(b.t.taskname) != null) ? Number(this.getTaskProgress(b.t.taskname)) : -1;
|
||||||
|
cmp = ap - bp;
|
||||||
|
} else if (by === 'update_time') {
|
||||||
|
// 按任务最近转存时间排序
|
||||||
|
const aRecord = this.taskLatestRecords[a.t.taskname];
|
||||||
|
const bRecord = this.taskLatestRecords[b.t.taskname];
|
||||||
|
const aTime = aRecord && aRecord.full ? new Date(aRecord.full).getTime() : 0;
|
||||||
|
const bTime = bRecord && bRecord.full ? new Date(bRecord.full).getTime() : 0;
|
||||||
|
cmp = aTime - bTime;
|
||||||
|
} else {
|
||||||
|
// 按任务名称中的编号排序(解析 #XX 格式)
|
||||||
|
const aNum = parseInt((a.t.taskname || '').match(/^#?(\d+)/)?.[1] || '0');
|
||||||
|
const bNum = parseInt((b.t.taskname || '').match(/^#?(\d+)/)?.[1] || '0');
|
||||||
|
cmp = aNum - bNum;
|
||||||
|
}
|
||||||
|
if (cmp === 0) cmp = a.idx - b.idx; // 稳定
|
||||||
|
return cmp * factor;
|
||||||
|
});
|
||||||
|
// 返回带原始索引的任务对象
|
||||||
|
return withIndex.map(x => ({ ...x.t, __originalIndex: x.idx }));
|
||||||
|
} catch (e) {
|
||||||
|
return (this.formData.tasklist || []).map((t, idx) => ({ ...t, __originalIndex: idx }));
|
||||||
|
}
|
||||||
|
},
|
||||||
// 选择日历日期
|
// 选择日历日期
|
||||||
selectCalendarDate(day) {
|
selectCalendarDate(day) {
|
||||||
try {
|
try {
|
||||||
@ -6022,8 +6158,11 @@
|
|||||||
return task;
|
return task;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 直接赋值,排序通过计算属性处理
|
||||||
|
this.formData.tasklist = config_data.tasklist || [];
|
||||||
|
|
||||||
// 获取所有任务父目录
|
// 获取所有任务父目录
|
||||||
config_data.tasklist.forEach(item => {
|
this.formData.tasklist.forEach(item => {
|
||||||
parentDir = this.getParentDirectory(item.savepath)
|
parentDir = this.getParentDirectory(item.savepath)
|
||||||
if (!this.taskDirs.includes(parentDir))
|
if (!this.taskDirs.includes(parentDir))
|
||||||
this.taskDirs.push(parentDir);
|
this.taskDirs.push(parentDir);
|
||||||
@ -6202,6 +6341,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.formData = config_data;
|
this.formData = config_data;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.configModified = false;
|
this.configModified = false;
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user