大唐全书地图
This commit is contained in:
205
frontend/App.vue
205
frontend/App.vue
@@ -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 }} | 已加载 {{ loadedCount }} / 40 卷
|
||||
当前:{{ currentVolLabel }} | 已加载 {{ 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
|
||||
|
||||
Reference in New Issue
Block a user