mirror of
https://github.com/Cp0204/quark-auto-save.git
synced 2026-01-12 15:20:44 +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 {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
margin-top: 0px; /* 上移8px(从8px调整为0px) */
|
||||
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 class="calendar-category-buttons tasklist-type-filter">
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary calendar-category-btn"
|
||||
:class="{ active: tasklist.selectedType === 'all' }"
|
||||
@click="selectTasklistType('all')">
|
||||
全部
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary calendar-category-btn"
|
||||
v-for="type in tasklist.contentTypes"
|
||||
:key="'tasklist-'+type"
|
||||
:class="{ active: tasklist.selectedType === type }"
|
||||
@click="selectTasklistType(type)">
|
||||
{{ getContentTypeDisplayName(type) }}
|
||||
</button>
|
||||
<!-- 任务列表:类型筛选按钮和排序组件(复用追剧日历移动端样式) -->
|
||||
<div class="row mb-3 tasklist-header-row">
|
||||
<div class="col-lg-8 col-md-6">
|
||||
<!-- 类型筛选按钮 -->
|
||||
<div class="calendar-category-buttons tasklist-type-filter">
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary calendar-category-btn"
|
||||
:class="{ active: tasklist.selectedType === 'all' }"
|
||||
@click="selectTasklistType('all')">
|
||||
全部
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-outline-secondary calendar-category-btn"
|
||||
v-for="type in tasklist.contentTypes"
|
||||
:key="'tasklist-'+type"
|
||||
: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 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)">
|
||||
<hr>
|
||||
<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="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">
|
||||
<span v-if="key==='latest_transfer_file' && formData.button_display.latest_transfer_file !== 'disabled' && taskLatestFiles[task.taskname]"
|
||||
class="task-latest-file"
|
||||
@ -2969,6 +2991,18 @@
|
||||
selectedType: (localStorage.getItem('tasklist_selected_type') || 'all'),
|
||||
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监听器
|
||||
calendarResizeHandler: null,
|
||||
// 日历自动检测更新相关
|
||||
@ -3054,6 +3088,35 @@
|
||||
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() {
|
||||
if (!this.calendar.tasks || this.calendar.tasks.length === 0) {
|
||||
@ -3519,6 +3582,79 @@
|
||||
this.stopCalendarAutoWatch();
|
||||
},
|
||||
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) {
|
||||
try {
|
||||
@ -6022,8 +6158,11 @@
|
||||
return task;
|
||||
});
|
||||
|
||||
// 直接赋值,排序通过计算属性处理
|
||||
this.formData.tasklist = config_data.tasklist || [];
|
||||
|
||||
// 获取所有任务父目录
|
||||
config_data.tasklist.forEach(item => {
|
||||
this.formData.tasklist.forEach(item => {
|
||||
parentDir = this.getParentDirectory(item.savepath)
|
||||
if (!this.taskDirs.includes(parentDir))
|
||||
this.taskDirs.push(parentDir);
|
||||
@ -6202,6 +6341,7 @@
|
||||
}
|
||||
}
|
||||
this.formData = config_data;
|
||||
|
||||
setTimeout(() => {
|
||||
this.configModified = false;
|
||||
}, 100);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user