大唐全书地图

This commit is contained in:
龙澳
2026-03-31 14:19:06 +08:00
parent 0a18caaecb
commit f26c930099
15 changed files with 8545 additions and 438 deletions

View File

@@ -46,7 +46,7 @@
class="chapter-slider"
v-model.number="currentVol"
:min="1"
:max="40"
:max="63"
@input="onVolSliderChange"
>
<div class="vol-ticks">
@@ -59,7 +59,7 @@
</div>
</div>
<div class="vol-info">
当前{{ currentVolLabel }} &nbsp;|&nbsp; 已加载 {{ loadedCount }} / 40
当前{{ currentVolLabel }} &nbsp;|&nbsp; 已加载 {{ loadedCount }} / 63
</div>
<!-- 关键事件列表 -->
@@ -93,8 +93,8 @@
<!-- 加载进度条 -->
<div class="loading-bar" v-if="isLoading">
<div class="loading-inner" :style="{ width: (loadedCount / 40 * 100) + '%' }"></div>
<span class="loading-text">加载数据中 {{ loadedCount }}/40 </span>
<div class="loading-inner" :style="{ width: (loadedCount / 63 * 100) + '%' }"></div>
<span class="loading-text">加载数据中 {{ loadedCount }}/63 </span>
</div>
</div>
</template>
@@ -105,14 +105,9 @@ import L from 'leaflet'
import 'leaflet/dist/leaflet.css'
// ── 常量 ────────────────────────────────────────────────────
const TOTAL_VOLS = 40
const SPLIT_VOL = 21 // 从该卷起寇仲/徐子陵分离
const TOTAL_VOLS = 63
// 合并路线名称(卷 1-20 使用)
const MERGED_NAME = '寇仲 & 徐子陵'
const MERGED_COLOR = '#FF6347'
// 分离路线定义(卷 21+ 使用)
// 分离路线定义(全卷 1-63 使用)
const KOUZHONG_NAME = '寇仲'
const KOUZHONG_COLOR = '#FF4500'
const XUZILING_NAME = '徐子陵'
@@ -120,7 +115,7 @@ const XUZILING_COLOR = '#FFA07A'
// ── 响应式状态 ──────────────────────────────────────────────
const allData = ref([]) // 原始卷数据index 0 = vol01
const currentVol = ref(1) // 1-40
const currentVol = ref(1) // 1-63
const selectedRoutes = ref([])
const selectedFaction = ref(null)
const isLoading = ref(true)
@@ -135,15 +130,17 @@ let layerGroups = {
events: null
}
// ── 滑块刻度每10卷一个─────────────────────────────────
// 滑块范围 1-40,共 39
// pct = (vol - 1) / (40 - 1) * 100
// ── 滑块刻度(每10卷一个─────────────────────────────────
// 滑块范围 1-63,共 62
// pct = (vol - 1) / (63 - 1) * 100
const volTicks = [
{ vol: 1, label: '卷一', pct: 0 },
{ vol: 10, label: '卷十', pct: (9 / 39 * 100).toFixed(3) * 1 },
{ vol: 20, label: '卷二十', pct: (19 / 39 * 100).toFixed(3) * 1 },
{ vol: 30, label: '卷三十', pct: (29 / 39 * 100).toFixed(3) * 1 },
{ vol: 40, label: '卷四十', pct: 100 }
{ vol: 1, label: '卷一', pct: 0 },
{ vol: 10, label: '卷十', pct: (9 / 62 * 100).toFixed(3) * 1 },
{ vol: 20, label: '卷二十', pct: (19 / 62 * 100).toFixed(3) * 1 },
{ vol: 30, label: '卷三十', pct: (29 / 62 * 100).toFixed(3) * 1 },
{ vol: 40, label: '卷四十', pct: (39 / 62 * 100).toFixed(3) * 1 },
{ vol: 50, label: '卷五十', pct: (49 / 62 * 100).toFixed(3) * 1 },
{ vol: 63, label: '卷六十三', pct: 100 }
]
// ── 计算属性:当前卷标签 ────────────────────────────────────
@@ -181,137 +178,27 @@ const mergedFactions = computed(() => {
})
/**
* 合并人物路线,处理寇仲/徐子陵在 vol21 前后的分合逻辑:
*
* 卷 1-20数据中是 "寇仲 & 徐子陵",直接按名合并。
*
* 卷 21+(当 currentVol >= SPLIT_VOL
* - 数据中是独立的 "寇仲" 和 "徐子陵"。
* - 前20卷的 "寇仲 & 徐子陵" 路线同时挂到这两条线上,
* 这样路线就连续(两人共同行走的历史 → 各自后续独立路线)。
*
* 当 currentVol < SPLIT_VOL只展示合并路线。
* 当 currentVol >= SPLIT_VOL只展示分离路线前20卷合并段分别并入两人。
* 合并人物路线(全卷 1-63,寇仲徐子陵全程分开显示)
*/
const mergedRoutes = computed(() => {
const isSplit = currentVol.value >= SPLIT_VOL
if (!isSplit) {
// ── 模式一:合并路线(卷 1-20──────────────────────────
const charMap = new Map()
for (let i = 0; i < currentVol.value; i++) {
const d = allData.value[i]
if (!d) continue
const vol = i + 1
for (const r of (d.character_routes || [])) {
const name = r.character
if (!charMap.has(name)) {
charMap.set(name, { character: name, color: r.color, route: [] })
}
const entry = charMap.get(name)
entry.color = r.color
for (const pt of (r.route || [])) {
entry.route.push({ ...pt, vol })
}
const charMap = new Map()
for (let i = 0; i < currentVol.value; i++) {
const d = allData.value[i]
if (!d) continue
const vol = i + 1
for (const r of (d.character_routes || [])) {
const name = r.character
if (!charMap.has(name)) {
charMap.set(name, { character: name, color: r.color, route: [] })
}
const entry = charMap.get(name)
entry.color = r.color
for (const pt of (r.route || [])) {
entry.route.push({ ...pt, vol })
}
}
return Array.from(charMap.values())
} else {
// ── 模式二:分离路线(卷 21+)───────────────────────────
// 先收集前20卷的合并路线路点
const earlyMergedPts = []
for (let i = 0; i < SPLIT_VOL - 1; i++) {
const d = allData.value[i]
if (!d) continue
const vol = i + 1
for (const r of (d.character_routes || [])) {
if (r.character === MERGED_NAME) {
for (const pt of (r.route || [])) {
earlyMergedPts.push({ ...pt, vol })
}
}
}
}
// 再按角色名合并 vol21+ 的独立路线
const splitMap = new Map()
// 初始化两条分离路线,并把早期合并路点作为前缀
const initSplitRoute = (name, color) => {
splitMap.set(name, {
character: name,
color,
route: [...earlyMergedPts] // 共享早期合并路点
})
}
initSplitRoute(KOUZHONG_NAME, KOUZHONG_COLOR)
initSplitRoute(XUZILING_NAME, XUZILING_COLOR)
// 收集其他角色(非寇仲/徐子陵)在 vol21+ 中的路线
const otherMap = new Map()
for (let i = SPLIT_VOL - 1; i < currentVol.value; i++) {
const d = allData.value[i]
if (!d) continue
const vol = i + 1
for (const r of (d.character_routes || [])) {
const name = r.character
if (name === KOUZHONG_NAME || name === XUZILING_NAME) {
const entry = splitMap.get(name)
entry.color = r.color
for (const pt of (r.route || [])) {
entry.route.push({ ...pt, vol })
}
} else {
// 其他角色正常合并
if (!otherMap.has(name)) {
otherMap.set(name, { character: name, color: r.color, route: [] })
}
const entry = otherMap.get(name)
entry.color = r.color
for (const pt of (r.route || [])) {
entry.route.push({ ...pt, vol })
}
}
}
}
// 同时也收集前20卷中其他角色非寇仲&徐子陵合并)的路线
const otherEarlyMap = new Map()
for (let i = 0; i < SPLIT_VOL - 1; i++) {
const d = allData.value[i]
if (!d) continue
const vol = i + 1
for (const r of (d.character_routes || [])) {
if (r.character === MERGED_NAME) continue
const name = r.character
if (!otherEarlyMap.has(name)) {
otherEarlyMap.set(name, { character: name, color: r.color, route: [] })
}
const entry = otherEarlyMap.get(name)
entry.color = r.color
for (const pt of (r.route || [])) {
entry.route.push({ ...pt, vol })
}
}
}
// 合并早期其他角色和后期其他角色
const allOther = new Map([...otherEarlyMap])
for (const [name, entry] of otherMap) {
if (allOther.has(name)) {
allOther.get(name).color = entry.color
allOther.get(name).route.push(...entry.route)
} else {
allOther.set(name, entry)
}
}
return [
...Array.from(splitMap.values()),
...Array.from(allOther.values())
]
}
return Array.from(charMap.values())
})
/**
@@ -337,35 +224,9 @@ onMounted(async () => {
// 监听 currentVol 变化重绘
watch(currentVol, () => {
syncSelectedRoutesOnModeChange()
drawAll()
})
/**
* 切换到卷21时将已选中的 "寇仲 & 徐子陵" 替换为两个分离路线;
* 切换回20以前时将分离路线替换为合并路线。
*/
function syncSelectedRoutesOnModeChange() {
const isSplit = currentVol.value >= SPLIT_VOL
const hasMerged = selectedRoutes.value.includes(MERGED_NAME)
const hasKouzhong = selectedRoutes.value.includes(KOUZHONG_NAME)
const hasXuziling = selectedRoutes.value.includes(XUZILING_NAME)
if (isSplit && hasMerged) {
// 合并 → 分离:把合并路线换成两条分离路线
const idx = selectedRoutes.value.indexOf(MERGED_NAME)
selectedRoutes.value.splice(idx, 1, KOUZHONG_NAME, XUZILING_NAME)
} else if (!isSplit && (hasKouzhong || hasXuziling)) {
// 分离 → 合并:把两条分离路线换成合并路线
selectedRoutes.value = selectedRoutes.value.filter(
n => n !== KOUZHONG_NAME && n !== XUZILING_NAME
)
if (!selectedRoutes.value.includes(MERGED_NAME)) {
selectedRoutes.value.push(MERGED_NAME)
}
}
}
// ── 数据加载 ────────────────────────────────────────────────
async function loadAllData() {
isLoading.value = true