引入二级大纲功能,新增父子节点关系及子节点排序,同时调整API与UI以支持嵌套大纲管理

This commit is contained in:
2026-03-22 16:53:33 +08:00
parent 20c759150a
commit 098ff4e16d
33 changed files with 2516 additions and 381 deletions

View File

@@ -1,9 +1,9 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { ref, inject, onMounted, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { fetchNovel } from '@/api/novels'
import { fetchWorldSetting, saveWorldSetting } from '@/api/worldSetting'
import { useToast } from '@/composables/useToast'
import { useToolRefresh } from '@/composables/useToolRefresh'
import type { Novel } from '@/types/novel'
import BaseButton from '@/components/ui/BaseButton.vue'
import BaseTextarea from '@/components/ui/BaseTextarea.vue'
@@ -12,8 +12,9 @@ const route = useRoute()
const router = useRouter()
const toast = useToast()
const novelId = Number(route.params.id)
const novel = ref<Novel | null>(null)
const novelId = inject<number>('workspace-novel-id', Number(route.params.id))
const novel = inject<import('vue').Ref<Novel | null>>('workspace-novel', ref(null))
const subtitle = inject<import('vue').Ref<string>>('workspace-subtitle', ref(''))
const content = ref('')
const loading = ref(true)
const saving = ref(false)
@@ -26,14 +27,16 @@ const contentHint = computed(() => {
return `${contentLength.value}`
})
// 世界观页不需要副标题
subtitle.value = ''
// AI 工具执行后自动刷新世界观数据
useToolRefresh(['get_world_setting', 'save_world_setting'], load)
async function load() {
loading.value = true
try {
const [n, ws] = await Promise.all([
fetchNovel(novelId),
fetchWorldSetting(novelId),
])
novel.value = n
const ws = await fetchWorldSetting(novelId)
content.value = ws.content
} catch {
toast.error('加载失败')
@@ -64,15 +67,8 @@ onMounted(load)
</script>
<template>
<div v-if="!loading && novel" class="world-view">
<div class="world-header">
<BaseButton variant="ghost" @click="router.push({ name: 'novel-detail', params: { id: novelId } })">
&larr; 返回
</BaseButton>
<div class="header-center">
<h1 class="world-title">{{ novel.title }}</h1>
<p class="world-subtitle">世界观设定</p>
</div>
<div v-if="!loading" class="world-view">
<Teleport to="#workspace-actions">
<BaseButton
variant="primary"
:loading="saving"
@@ -81,7 +77,7 @@ onMounted(load)
>
保存
</BaseButton>
</div>
</Teleport>
<div class="editor-container">
<div class="editor-hint">
@@ -124,33 +120,6 @@ onMounted(load)
animation: fadeInUp var(--duration-normal) var(--ease-out-smooth) both;
}
.world-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--space-md);
margin-bottom: var(--space-2xl);
}
.header-center {
flex: 1;
text-align: center;
}
.world-title {
font-family: var(--font-display);
font-size: 1.8rem;
font-weight: 700;
color: var(--ink-900);
line-height: 1.3;
}
.world-subtitle {
font-size: 0.85rem;
color: var(--ink-400);
margin-top: var(--space-xs);
}
.editor-container {
background: var(--paper-50);
border: 1px solid var(--paper-200);