feat:ScriptDetailView 改造为 chat-bubble 流程视图复刻生成时展示
This commit is contained in:
@@ -10,55 +10,57 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<scroll-view class="scroll" scroll-y :show-scrollbar="false">
|
<scroll-view class="scroll" scroll-y :show-scrollbar="false">
|
||||||
<view class="hero-card kos-card">
|
<view class="detail-flow">
|
||||||
<text class="eyebrow">PARALLEL LIFE</text>
|
<view
|
||||||
<text class="script-title">{{ script?.title || '未命名剧本' }}</text>
|
v-for="msg in resultMessages"
|
||||||
<text class="script-summary">{{ script?.summary || '命运正在整理章节。' }}</text>
|
:key="msg.id"
|
||||||
<view v-if="userWish" class="wish-block">
|
>
|
||||||
<text class="wish-label">💭 用户心愿</text>
|
<!-- 用户心愿气泡 -->
|
||||||
<text class="wish-text">{{ userWish }}</text>
|
<view v-if="msg.role === 'user' && msg.kind === 'text'" class="chat-bubble user">
|
||||||
|
<text>{{ msg.content }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="stats">
|
|
||||||
<view class="stat">
|
<!-- 澄清问答卡片(只读,已回答状态) -->
|
||||||
<text class="stat-value">{{ script?.style || '爽文' }}</text>
|
<view v-else-if="msg.kind === 'card'" class="chat-bubble system">
|
||||||
<text class="stat-label">风格</text>
|
<view class="card-answered">
|
||||||
|
<text v-if="msg.card?.question" class="card-question-text">{{ msg.card.question }}</text>
|
||||||
|
<text class="card-answer-text">已回答:{{ msg.answer || '未回答' }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="stat">
|
|
||||||
<text class="stat-value">{{ lengthText }}</text>
|
|
||||||
<text class="stat-label">篇幅</text>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="stat">
|
|
||||||
<text class="stat-value">{{ script?.wordCount || 0 }}</text>
|
<!-- 大纲(beats 列表,只读) -->
|
||||||
<text class="stat-label">字数</text>
|
<view v-else-if="msg.kind === 'outline'" class="chat-bubble system">
|
||||||
|
<view v-if="msg.outline">
|
||||||
|
<view v-if="msg.outline.title" class="outline-title">
|
||||||
|
<text>{{ msg.outline.title }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="msg.outline.logline" class="outline-logline">
|
||||||
|
<text>{{ msg.outline.logline }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="Array.isArray(msg.outline.beats)" class="outline-beats">
|
||||||
|
<view v-for="(beat, idx) in msg.outline.beats" :key="idx" class="beat-item">
|
||||||
|
<text class="beat-order">{{ beat.order || idx + 1 }}</text>
|
||||||
|
<view class="beat-content">
|
||||||
|
<text class="beat-title">{{ beat.title || '' }}</text>
|
||||||
|
<text v-if="beat.summary" class="beat-summary">{{ beat.summary }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="msg.outline.ending" class="outline-ending">
|
||||||
|
<text class="ending-label">结局</text>
|
||||||
|
<text class="ending-text">{{ msg.outline.ending }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="tabs kos-card">
|
<!-- 小说正文(Markdown 渲染) -->
|
||||||
<view class="tab" :class="{ active: activeTab === 'content' }" @click="activeTab = 'content'">正文</view>
|
<view v-else-if="msg.kind === 'novel'" class="chat-bubble system novel-bubble">
|
||||||
<view class="tab" :class="{ active: activeTab === 'outline' }" @click="activeTab = 'outline'">大纲</view>
|
<Markdown :content="msg.content" />
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="activeTab === 'content'" class="article-card kos-card">
|
<view v-if="resultMessages.length === 0" class="empty-flow">
|
||||||
<Markdown :content="fullContent" />
|
<text>暂无剧本内容</text>
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-else-if="activeTab === 'outline'" class="outline-list">
|
|
||||||
<!-- 优先展示从后端保存的真实大纲(来自 conversation.message.metadata) -->
|
|
||||||
<view v-if="outlineFromMeta && outlineFromMeta.length" v-for="(item, index) in outlineFromMeta" :key="'meta-' + index" class="outline-card kos-card">
|
|
||||||
<view class="outline-node">{{ index + 1 }}</view>
|
|
||||||
<view class="outline-body">
|
|
||||||
<text class="outline-title">{{ item.title }}</text>
|
|
||||||
<text class="outline-text">{{ item.text }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- fallback 到本地解析 -->
|
|
||||||
<view v-else v-for="(item, index) in outline" :key="'local-' + index" class="outline-card kos-card">
|
|
||||||
<view class="outline-node">{{ index + 1 }}</view>
|
|
||||||
<view class="outline-body">
|
|
||||||
<text class="outline-title">{{ item.title }}</text>
|
|
||||||
<text class="outline-text">{{ item.text }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
@@ -81,49 +83,21 @@ import Markdown from '../../components/Markdown.vue'
|
|||||||
import analytics from '../../services/analytics.js'
|
import analytics from '../../services/analytics.js'
|
||||||
import { useTtsPlayer } from '../../composables/useTtsPlayer.js'
|
import { useTtsPlayer } from '../../composables/useTtsPlayer.js'
|
||||||
import { useMenuButtonSafeArea } from '../../composables/useMenuButtonSafeArea.js'
|
import { useMenuButtonSafeArea } from '../../composables/useMenuButtonSafeArea.js'
|
||||||
|
import { listMessagesByConversation } from '../../services/scriptChat.js'
|
||||||
|
|
||||||
const store = useAppStore()
|
const store = useAppStore()
|
||||||
const activeTab = ref('content')
|
|
||||||
const scriptId = ref('')
|
const scriptId = ref('')
|
||||||
const script = ref(null)
|
const script = ref(null)
|
||||||
|
const resultMessages = ref([])
|
||||||
const pagePath = '/pages/main/ScriptDetailView'
|
const pagePath = '/pages/main/ScriptDetailView'
|
||||||
const ttsPlayer = useTtsPlayer({ pagePath })
|
const ttsPlayer = useTtsPlayer({ pagePath })
|
||||||
const { capsuleTopReservePx, topbarStyle } = useMenuButtonSafeArea({ extraTopPx: 10 })
|
const { capsuleTopReservePx, topbarStyle } = useMenuButtonSafeArea({ extraTopPx: 10 })
|
||||||
|
|
||||||
const fullContent = computed(() => script.value?.content || '暂无正文内容。')
|
|
||||||
const lengthText = computed(() => {
|
const lengthText = computed(() => {
|
||||||
const map = { short: '短篇', medium: '中篇', long: '长篇' }
|
const map = { short: '短篇', medium: '中篇', long: '长篇' }
|
||||||
return map[script.value?.length] || script.value?.length || '中篇'
|
return map[script.value?.length] || script.value?.length || '中篇'
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户心愿原文(从 EpicScript.theme 字段读取)
|
|
||||||
*/
|
|
||||||
const userWish = computed(() => script.value?.theme || '')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 大纲(从 plotJson.stages.outline 读取后端真实保存的 outline JSON)
|
|
||||||
* 后端 ShortNovelServiceImpl 在 novel_done 时会把 outline_created 事件累积到 metadata.stages.outline
|
|
||||||
*/
|
|
||||||
const outlineFromMeta = computed(() => {
|
|
||||||
const stages = script.value?.plotJson?.stages
|
|
||||||
if (!stages || !stages.outline) return []
|
|
||||||
const outline = stages.outline
|
|
||||||
const items = []
|
|
||||||
if (outline.title) items.push({ title: '标题', text: outline.title })
|
|
||||||
if (outline.logline) items.push({ title: '简介', text: outline.logline })
|
|
||||||
if (Array.isArray(outline.beats)) {
|
|
||||||
outline.beats.forEach((beat, i) => {
|
|
||||||
items.push({
|
|
||||||
title: beat.title || `章节 ${i + 1}`,
|
|
||||||
text: beat.text || beat.description || JSON.stringify(beat)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (outline.ending) items.push({ title: '结局', text: outline.ending })
|
|
||||||
return items
|
|
||||||
})
|
|
||||||
|
|
||||||
const detailTtsActionText = computed(() => {
|
const detailTtsActionText = computed(() => {
|
||||||
if (!script.value?.id) return '播放'
|
if (!script.value?.id) return '播放'
|
||||||
if (ttsPlayer.loading.value) return '生成中'
|
if (ttsPlayer.loading.value) return '生成中'
|
||||||
@@ -134,39 +108,63 @@ const detailTtsIcon = computed(() => {
|
|||||||
return ttsPlayer.playing.value ? 'Ⅱ' : '▶'
|
return ttsPlayer.playing.value ? 'Ⅱ' : '▶'
|
||||||
})
|
})
|
||||||
|
|
||||||
const outline = computed(() => {
|
/**
|
||||||
const text = fullContent.value
|
* 从后端 message 列表重建统一对话消息流
|
||||||
const parts = text.split(/\n{2,}/).filter(Boolean)
|
* 复刻 ScriptView 生成时的 chat-bubble 展示结构
|
||||||
if (parts.length > 1) {
|
*/
|
||||||
return parts.slice(0, 8).map((part, index) => {
|
const rebuildResultMessages = (scriptData, messages) => {
|
||||||
const clean = part.replace(/[#>*_`]/g, '').trim()
|
const result = []
|
||||||
const lines = clean.split('\n').filter(Boolean)
|
|
||||||
return {
|
// 1. 用户心愿作为首条 user 消息(如果 messages 中没有 user 心愿消息则用 theme 补)
|
||||||
title: lines[0]?.replace(/[【】]/g, '') || `章节 ${index + 1}`,
|
const hasUserWishInMessages = messages.some(m => m.sender === 'user' && m.type === 'chat')
|
||||||
text: lines.slice(1).join(' ').slice(0, 120) || clean.slice(0, 120)
|
if (!hasUserWishInMessages && scriptData?.theme) {
|
||||||
}
|
result.push({
|
||||||
|
id: `wish-${scriptData.id}`,
|
||||||
|
role: 'user',
|
||||||
|
kind: 'text',
|
||||||
|
content: scriptData.theme
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
// 2. 按 messageOrder 遍历 messages,转换为对应 kind
|
||||||
{ title: '起点', text: script.value?.theme || '从真实的人生经验出发。' },
|
messages.forEach(m => {
|
||||||
{ title: '转折', text: '关键机会出现,旧有困境开始松动。' },
|
if (m.type === 'clarification_question') {
|
||||||
{ title: '高光', text: '主角完成一次真正的选择,并看见新的自我。' },
|
let card = {}
|
||||||
{ title: '回响', text: '剧本落回现实,转化为可执行路径。' }
|
try { card = JSON.parse(m.content || '{}') } catch (e) { card = {} }
|
||||||
]
|
result.push({ id: m.id, role: 'assistant', kind: 'card', card, answer: '', submitted: true })
|
||||||
})
|
} else if (m.type === 'clarification_answer') {
|
||||||
|
const lastCard = [...result].reverse().find(x => x.kind === 'card')
|
||||||
|
if (lastCard) {
|
||||||
|
lastCard.answer = m.content || '未回答'
|
||||||
|
} else {
|
||||||
|
// 无对应 question,作为独立 user 消息
|
||||||
|
result.push({ id: m.id, role: 'user', kind: 'text', content: m.content || '' })
|
||||||
|
}
|
||||||
|
} else if (m.type === 'outline') {
|
||||||
|
let outline = {}
|
||||||
|
try { outline = JSON.parse(m.content || '{}') } catch (e) { outline = {} }
|
||||||
|
result.push({ id: m.id, role: 'assistant', kind: 'outline', outline })
|
||||||
|
} else if (m.type === 'script') {
|
||||||
|
result.push({ id: m.id, role: 'assistant', kind: 'novel', content: m.content || '' })
|
||||||
|
} else if (m.type === 'system') {
|
||||||
|
// 跳过系统欢迎消息,不展示
|
||||||
|
} else if (m.type === 'chat' && m.sender === 'user') {
|
||||||
|
result.push({ id: m.id, role: 'user', kind: 'text', content: m.content || '' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const loadScript = async () => {
|
// 3. 如果 messages 中没有 script 类型消息,fallback 到 plotJson.fullContent
|
||||||
if (!scriptId.value) return
|
const hasNovel = result.some(m => m.kind === 'novel')
|
||||||
ttsPlayer.reset()
|
if (!hasNovel && scriptData?.plotJson?.fullContent) {
|
||||||
script.value = store.getScriptById(scriptId.value)
|
result.push({
|
||||||
if (!script.value) {
|
id: `fallback-novel-${scriptData.id}`,
|
||||||
await store.fetchScripts()
|
role: 'assistant',
|
||||||
script.value = store.getScriptById(scriptId.value)
|
kind: 'novel',
|
||||||
}
|
content: scriptData.plotJson.fullContent
|
||||||
if (script.value?.id) {
|
})
|
||||||
ttsPlayer.prewarmSource(script.value.id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
const continueCurrent = () => {
|
const continueCurrent = () => {
|
||||||
@@ -194,9 +192,32 @@ const goBack = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
// scriptId 从路由参数获取
|
||||||
const pages = getCurrentPages()
|
const pages = getCurrentPages()
|
||||||
scriptId.value = pages[pages.length - 1]?.options?.id || ''
|
const currentPage = pages[pages.length - 1]
|
||||||
await loadScript()
|
scriptId.value = currentPage?.options?.id || currentPage?.$page?.options?.id || ''
|
||||||
|
|
||||||
|
// 1. 加载 script 数据
|
||||||
|
await store.fetchScripts()
|
||||||
|
script.value = store.getScriptById(scriptId.value)
|
||||||
|
|
||||||
|
// 2. 加载 conversation messages
|
||||||
|
const conversationId = script.value?.conversationId
|
||||||
|
if (conversationId) {
|
||||||
|
try {
|
||||||
|
const res = await listMessagesByConversation({ conversationId, includeVersions: false })
|
||||||
|
const messages = Array.isArray(res?.data) ? res.data : []
|
||||||
|
resultMessages.value = rebuildResultMessages(script.value, messages)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[ScriptDetailView] 加载 messages 失败:', e)
|
||||||
|
// 降级:仅展示心愿 + 正文
|
||||||
|
resultMessages.value = rebuildResultMessages(script.value, [])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 无 conversationId,降级展示
|
||||||
|
resultMessages.value = rebuildResultMessages(script.value, [])
|
||||||
|
}
|
||||||
|
|
||||||
analytics.trackPageView(pagePath, { script_id: scriptId.value })
|
analytics.trackPageView(pagePath, { script_id: scriptId.value })
|
||||||
analytics.track('script_detail_view', {
|
analytics.track('script_detail_view', {
|
||||||
script_id: scriptId.value,
|
script_id: scriptId.value,
|
||||||
@@ -283,168 +304,99 @@ onUnmounted(() => {
|
|||||||
padding: 0 30rpx;
|
padding: 0 30rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-card {
|
/* chat-bubble 流程视图样式 */
|
||||||
border-radius: 32rpx;
|
.detail-flow {
|
||||||
padding: 36rpx 30rpx;
|
padding: 20rpx 0 160rpx;
|
||||||
}
|
}
|
||||||
|
.chat-bubble {
|
||||||
.eyebrow {
|
margin-bottom: 24rpx;
|
||||||
display: block;
|
padding: 24rpx;
|
||||||
color: #c186ff;
|
border-radius: 24rpx;
|
||||||
font-size: 18rpx;
|
|
||||||
letter-spacing: 6rpx;
|
|
||||||
}
|
}
|
||||||
|
.chat-bubble.user {
|
||||||
.script-title {
|
background: rgba(168, 85, 247, 0.12);
|
||||||
display: block;
|
align-self: flex-end;
|
||||||
margin-top: 14rpx;
|
margin-left: 80rpx;
|
||||||
color: #fff;
|
|
||||||
font-size: 44rpx;
|
|
||||||
line-height: 1.18;
|
|
||||||
font-weight: 900;
|
|
||||||
}
|
}
|
||||||
|
.chat-bubble.system {
|
||||||
.script-summary {
|
|
||||||
display: block;
|
|
||||||
margin-top: 18rpx;
|
|
||||||
color: rgba(226, 216, 246, 0.7);
|
|
||||||
font-size: 25rpx;
|
|
||||||
line-height: 1.62;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wish-block {
|
|
||||||
margin-top: 24rpx;
|
|
||||||
padding: 20rpx 22rpx;
|
|
||||||
border-radius: 22rpx;
|
|
||||||
background: linear-gradient(135deg, rgba(168, 85, 247, 0.16), rgba(232, 121, 249, 0.08));
|
|
||||||
border: 1rpx solid rgba(168, 85, 247, 0.24);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wish-label {
|
|
||||||
display: block;
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: #c186ff;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 2rpx;
|
|
||||||
margin-bottom: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wish-text {
|
|
||||||
display: block;
|
|
||||||
font-size: 26rpx;
|
|
||||||
line-height: 1.62;
|
|
||||||
color: rgba(255, 255, 255, 0.92);
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 16rpx;
|
|
||||||
margin-top: 28rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat {
|
|
||||||
padding: 18rpx 10rpx;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
text-align: center;
|
|
||||||
background: rgba(255, 255, 255, 0.06);
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
margin-right: 40rpx;
|
||||||
}
|
}
|
||||||
|
.card-answered {
|
||||||
.stat-value,
|
|
||||||
.stat-label {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-value {
|
|
||||||
color: #fff;
|
|
||||||
font-size: 24rpx;
|
|
||||||
font-weight: 800;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-label {
|
|
||||||
margin-top: 6rpx;
|
|
||||||
color: rgba(219, 207, 243, 0.58);
|
|
||||||
font-size: 19rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs {
|
|
||||||
height: 76rpx;
|
|
||||||
margin-top: 24rpx;
|
|
||||||
border-radius: 999rpx;
|
|
||||||
padding: 6rpx;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab {
|
|
||||||
border-radius: 999rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: rgba(224, 214, 243, 0.64);
|
|
||||||
font-size: 26rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab.active {
|
|
||||||
color: #fff;
|
|
||||||
background: linear-gradient(135deg, #a63cff, #6530ff);
|
|
||||||
box-shadow: 0 0 26rpx rgba(162, 71, 255, 0.48);
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-card {
|
|
||||||
margin: 24rpx 0 34rpx;
|
|
||||||
border-radius: 30rpx;
|
|
||||||
padding: 30rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.outline-list {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 18rpx;
|
gap: 12rpx;
|
||||||
margin: 24rpx 0 34rpx;
|
|
||||||
}
|
}
|
||||||
|
.card-question-text {
|
||||||
.outline-card {
|
font-size: 28rpx;
|
||||||
border-radius: 26rpx;
|
color: rgba(255, 255, 255, 0.9);
|
||||||
padding: 26rpx;
|
font-weight: 500;
|
||||||
display: flex;
|
|
||||||
gap: 20rpx;
|
|
||||||
}
|
}
|
||||||
|
.card-answer-text {
|
||||||
.outline-node {
|
font-size: 26rpx;
|
||||||
width: 48rpx;
|
color: rgba(193, 134, 255, 0.88);
|
||||||
height: 48rpx;
|
|
||||||
flex-shrink: 0;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 22rpx;
|
|
||||||
font-weight: 900;
|
|
||||||
background: linear-gradient(135deg, #a855ff, #4f46e5);
|
|
||||||
box-shadow: 0 0 22rpx rgba(168, 85, 255, 0.48);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.outline-body {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.outline-title {
|
.outline-title {
|
||||||
display: block;
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 27rpx;
|
margin-bottom: 12rpx;
|
||||||
font-weight: 900;
|
|
||||||
}
|
}
|
||||||
|
.outline-logline {
|
||||||
.outline-text {
|
font-size: 26rpx;
|
||||||
display: block;
|
color: rgba(255, 255, 255, 0.7);
|
||||||
margin-top: 10rpx;
|
margin-bottom: 16rpx;
|
||||||
color: rgba(223, 211, 245, 0.7);
|
}
|
||||||
|
.outline-beats {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
.beat-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
.beat-order {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
line-height: 1.6;
|
color: rgba(193, 134, 255, 0.88);
|
||||||
|
width: 40rpx;
|
||||||
|
}
|
||||||
|
.beat-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6rpx;
|
||||||
|
}
|
||||||
|
.beat-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
.beat-summary {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
.outline-ending {
|
||||||
|
margin-top: 16rpx;
|
||||||
|
padding-top: 16rpx;
|
||||||
|
border-top: 1rpx solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
.ending-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
margin-right: 12rpx;
|
||||||
|
}
|
||||||
|
.ending-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
.novel-bubble {
|
||||||
|
padding: 32rpx;
|
||||||
|
}
|
||||||
|
.empty-flow {
|
||||||
|
padding: 80rpx 0;
|
||||||
|
text-align: center;
|
||||||
|
color: rgba(255, 255, 255, 0.4);
|
||||||
|
font-size: 28rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-actions {
|
.bottom-actions {
|
||||||
|
|||||||
Reference in New Issue
Block a user