重构章节树组件,更新章节ID展示逻辑,移除节序号依赖,优化章节范围显示。调整内容页面树构建逻辑,新增热度排名映射,提升数据展示一致性和用户体验。
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 统一计算,前端只展示
|
||||
|
||||
Reference in New Issue
Block a user