feat: 增加友链申请、独立页面、投票/悬赏/抽奖帖与侧栏签到,并统一开发数据目录

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-27 06:23:54 +08:00
parent df19752a1e
commit 2208af7070
80 changed files with 9620 additions and 641 deletions

View File

@@ -0,0 +1,69 @@
import { arrayMove } from '@dnd-kit/sortable';
export const ADMIN_SORTABLE_MOVE_BUTTONS_THRESHOLD = 8;
export function reorderItems<T>(items: T[], from: number, to: number): T[] {
if (from < 0 || to < 0 || from >= items.length || to >= items.length || from === to) {
return items;
}
return arrayMove(items, from, to);
}
export function assignSortOrders<T extends { sort_order?: number }>(items: T[]): T[] {
return items.map((item, index) => ({ ...item, sort_order: index + 1 }));
}
export function diffSortOrderChanges<T extends { id: number; sort_order?: number }>(
before: T[],
after: T[],
): Array<{ id: number; sort_order: number }> {
const beforeMap = new Map(before.map(item => [item.id, item.sort_order ?? 0]));
return after
.filter(item => beforeMap.get(item.id) !== item.sort_order)
.map(item => ({ id: item.id, sort_order: item.sort_order ?? 0 }));
}
/** 将子集重排结果合并回完整列表,并重算 sort_order */
export function mergeReorderedSubset<T extends { id: number; sort_order?: number }>(
all: T[],
subsetBefore: T[],
subsetAfter: T[],
): T[] {
const sorted = [...all].sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0) || a.id - b.id);
const subsetIds = new Set(subsetBefore.map(item => item.id));
const result: T[] = [];
let subsetIdx = 0;
for (const item of sorted) {
if (subsetIds.has(item.id)) {
if (subsetIdx < subsetAfter.length) {
result.push(subsetAfter[subsetIdx++]);
}
} else {
result.push(item);
}
}
return assignSortOrders(result);
}
export async function persistSortOrderChanges<T extends { id: number; sort_order?: number }>(
before: T[],
reordered: T[],
updateItem: (item: T) => Promise<void>,
): Promise<T[]> {
const after = assignSortOrders(reordered);
const changes = diffSortOrderChanges(before, after);
for (const change of changes) {
const item = after.find(row => row.id === change.id);
if (item) await updateItem(item);
}
return after;
}
export function shouldShowSortableMoveButtons(
count: number,
mode: boolean | 'auto' = 'auto',
): boolean {
if (mode === true) return true;
if (mode === false) return false;
return count > ADMIN_SORTABLE_MOVE_BUTTONS_THRESHOLD;
}