重构章节树组件,更新章节ID展示逻辑,移除节序号依赖,优化章节范围显示。调整内容页面树构建逻辑,新增热度排名映射,提升数据展示一致性和用户体验。

This commit is contained in:
Alex-larget
2026-03-20 11:05:52 +08:00
parent e79152c80b
commit ecc4ded052
2 changed files with 33 additions and 43 deletions

View File

@@ -41,26 +41,16 @@ function isPrefacePart(p: PartItem) {
}
/** 篇内按章数量展示第1章 ~ 第n章仅章数与节数无关 */
function chapterRangeSubtitle(chapterCount: number): string {
if (chapterCount <= 0) return '暂无章节'
if (chapterCount === 1) return '第1章'
return `第1章 ~ 第${chapterCount}`
}
/** 全书节序号:跳过序言篇后从 1 连续编号(与列表拖拽顺序一致) */
function buildBodySectionOrderMap(parts: PartItem[]): Map<string, number> {
const m = new Map<string, number>()
let n = 0
for (const part of parts) {
if (isPrefacePart(part)) continue
for (const ch of part.chapters) {
for (const s of ch.sections) {
n += 1
m.set(s.id, n)
}
function sectionIdRangeSubtitle(part: PartItem): string {
const ids: string[] = []
for (const ch of part.chapters) {
for (const s of ch.sections) {
ids.push(s.id)
}
}
return m
if (ids.length === 0) return '暂无章节'
if (ids.length === 1) return ids[0]
return `${ids[0]}~${ids[ids.length - 1]}`
}
function parseDragData(data: string): { type: DragType; id: string } | null {
@@ -113,7 +103,7 @@ export function ChapterTree({
const [draggingItem, setDraggingItem] = useState<{ type: DragType; id: string } | null>(null)
const [dragOverTarget, setDragOverTarget] = useState<{ type: DragType; id: string } | null>(null)
const bodySectionOrderMap = useMemo(() => buildBodySectionOrderMap(parts), [parts])
// 章节ID/范围展示由数据本身决定,不再依赖“节序号”重新编号
const isDragging = (type: DragType, id: string) => draggingItem?.type === type && draggingItem?.id === id
const isDragOver = (type: DragType, id: string) => dragOverTarget?.type === type && dragOverTarget?.id === id
@@ -303,25 +293,16 @@ export function ChapterTree({
parts.slice(0, partIndex).filter((p) => !isPrefacePart(p)).length
const sectionTitleLine = (section: SectionItem) => {
const ord = bodySectionOrderMap.get(section.id)
if (ord != null) {
return (
<>
<span
className="text-gray-500 font-mono text-xs tabular-nums shrink-0 mr-1.5"
title={`节序号(不含序言篇)· id: ${section.id}`}
>
{ord}.
</span>
<span className="truncate">{section.title}</span>
</>
)
}
return (
<span className="truncate" title={section.id}>
{section.mid != null && section.mid > 0 ? `${section.mid}. ` : ''}
{section.title}
</span>
<>
<span
className="text-gray-500 font-mono text-xs tabular-nums shrink-0 mr-1.5 max-w-[72px] truncate"
title={`章节ID: ${section.id}`}
>
{section.id}
</span>
<span className="truncate">{section.title}</span>
</>
)
}
@@ -439,7 +420,7 @@ export function ChapterTree({
</div>
<div>
<h3 className="font-bold text-white text-base">{part.title}</h3>
<p className="text-xs text-gray-500 mt-0.5">{chapterRangeSubtitle(chapterCount)}</p>
<p className="text-xs text-gray-500 mt-0.5">{sectionIdRangeSubtitle(part)}</p>
</div>
</div>
<div
@@ -805,7 +786,7 @@ export function ChapterTree({
</div>
<div>
<h3 className="font-bold text-white text-base">{part.title}</h3>
<p className="text-xs text-gray-500 mt-0.5">{chapterRangeSubtitle(chapterCount)}</p>
<p className="text-xs text-gray-500 mt-0.5">{sectionIdRangeSubtitle(part)}</p>
</div>
</div>
<div

View File

@@ -1,4 +1,4 @@
import { useState, useRef, useEffect, useCallback } from 'react'
import { useState, useRef, useEffect, useCallback, useMemo } from 'react'
import toast from '@/utils/toast'
import {
Card,
@@ -129,7 +129,7 @@ interface EditingSection {
editionPremium?: boolean
}
function buildTree(sections: SectionListItem[]): Part[] {
function buildTree(sections: SectionListItem[], hotRankMap: Map<string, number>): Part[] {
const partMap = new Map<
string,
{ id: string; title: string; chapters: Map<string, { id: string; title: string; sections: Section[] }> }
@@ -157,7 +157,7 @@ function buildTree(sections: SectionListItem[]): Part[] {
clickCount: s.clickCount ?? 0,
payCount: s.payCount ?? 0,
hotScore: s.hotScore ?? 0,
hotRank: s.hotRank ?? 0,
hotRank: hotRankMap.get(s.id) ?? 0,
})
}
const parts = Array.from(partMap.values()).map((p) => ({
@@ -277,7 +277,16 @@ export function ContentPage() {
const [ckbLeadLoading, setCkbLeadLoading] = useState(false)
const richEditorRef = useRef<RichEditorRef>(null)
const tree = buildTree(sectionsList)
const hotRankMap = useMemo(() => {
const m = new Map<string, number>()
rankedSectionsList.forEach((s, idx) => {
// 排名展示从 1 开始避免出现“第0名”
m.set(s.id, idx + 1)
})
return m
}, [rankedSectionsList])
const tree = buildTree(sectionsList, hotRankMap)
const totalSections = sectionsList.length
// 内容排行榜:排序与置顶由后端 API 统一计算,前端只展示