|
|
@@ -31,14 +31,62 @@ const selectedVideo = computed(
|
|
|
() => videos.value.find((v) => v.vid === selectedVid.value) ?? null,
|
|
|
)
|
|
|
|
|
|
-const topicDisplay = computed(() => {
|
|
|
- const raw = selectedVideo.value?.final_topic_json
|
|
|
- if (!raw) return null
|
|
|
+interface TopicPoint {
|
|
|
+ title: string
|
|
|
+ description: string
|
|
|
+}
|
|
|
+
|
|
|
+interface TopicSection {
|
|
|
+ key: string
|
|
|
+ label: string
|
|
|
+ accent: string
|
|
|
+ items: TopicPoint[]
|
|
|
+}
|
|
|
+
|
|
|
+function parsePointsJson(raw: string | null | undefined): TopicPoint[] {
|
|
|
+ if (!raw) return []
|
|
|
try {
|
|
|
- return JSON.stringify(JSON.parse(raw), null, 2)
|
|
|
+ const parsed = JSON.parse(raw)
|
|
|
+ const list = Array.isArray(parsed) ? parsed : [parsed]
|
|
|
+ return list
|
|
|
+ .filter((item): item is Record<string, unknown> => !!item && typeof item === 'object')
|
|
|
+ .map((item) => ({
|
|
|
+ title: String(item['点'] ?? '').trim(),
|
|
|
+ description: String(item['点描述'] ?? '').trim(),
|
|
|
+ }))
|
|
|
+ .filter((item) => item.title || item.description)
|
|
|
} catch {
|
|
|
- return raw
|
|
|
+ return []
|
|
|
}
|
|
|
+}
|
|
|
+
|
|
|
+const topicSections = computed((): TopicSection[] | null => {
|
|
|
+ const video = selectedVideo.value
|
|
|
+ if (!video) return null
|
|
|
+
|
|
|
+ const sections: TopicSection[] = [
|
|
|
+ {
|
|
|
+ key: 'inspiration',
|
|
|
+ label: '灵感点',
|
|
|
+ accent: 'inspiration',
|
|
|
+ items: parsePointsJson(video.inspiration_points_json),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'purpose',
|
|
|
+ label: '目的点',
|
|
|
+ accent: 'purpose',
|
|
|
+ items: parsePointsJson(video.purpose_points_json),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: 'key',
|
|
|
+ label: '关键点',
|
|
|
+ accent: 'key',
|
|
|
+ items: parsePointsJson(video.key_points_json),
|
|
|
+ },
|
|
|
+ ]
|
|
|
+
|
|
|
+ if (sections.every((s) => s.items.length === 0)) return null
|
|
|
+ return sections
|
|
|
})
|
|
|
|
|
|
watch(
|
|
|
@@ -180,9 +228,31 @@ function selectVideo(video: DemandVideoItem) {
|
|
|
<section class="col col-topic">
|
|
|
<h3 class="col-title">选题结果</h3>
|
|
|
<template v-if="selectedVideo">
|
|
|
- <div v-if="topicDisplay" class="topic-card">
|
|
|
+ <div v-if="topicSections" class="topic-card">
|
|
|
<div class="topic-meta">Video {{ selectedVideo.vid }}</div>
|
|
|
- <pre class="topic-json">{{ topicDisplay }}</pre>
|
|
|
+ <div class="topic-body">
|
|
|
+ <section
|
|
|
+ v-for="section in topicSections"
|
|
|
+ :key="section.key"
|
|
|
+ class="topic-section"
|
|
|
+ :class="`accent-${section.accent}`"
|
|
|
+ >
|
|
|
+ <div class="section-head">
|
|
|
+ <span class="section-label">{{ section.label }}</span>
|
|
|
+ <span class="section-count">{{ section.items.length }}</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="section.items.length" class="point-list">
|
|
|
+ <article v-for="(item, idx) in section.items" :key="idx" class="point-item">
|
|
|
+ <div class="point-title-row">
|
|
|
+ <span class="point-index">{{ idx + 1 }}</span>
|
|
|
+ <h4 class="point-title">{{ item.title || '未命名' }}</h4>
|
|
|
+ </div>
|
|
|
+ <p v-if="item.description" class="point-desc">{{ item.description }}</p>
|
|
|
+ </article>
|
|
|
+ </div>
|
|
|
+ <div v-else class="section-empty">暂无</div>
|
|
|
+ </section>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
<div v-else class="empty">暂无选题结果</div>
|
|
|
</template>
|
|
|
@@ -210,7 +280,7 @@ function selectVideo(video: DemandVideoItem) {
|
|
|
}
|
|
|
|
|
|
.col-topic {
|
|
|
- width: 300px;
|
|
|
+ width: 380px;
|
|
|
}
|
|
|
|
|
|
.col-head {
|
|
|
@@ -413,7 +483,7 @@ function selectVideo(video: DemandVideoItem) {
|
|
|
border-radius: 12px;
|
|
|
background: linear-gradient(180deg, #faf5ff 0%, #fff 45%);
|
|
|
overflow: hidden;
|
|
|
- max-height: 420px;
|
|
|
+ max-height: 520px;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
}
|
|
|
@@ -425,20 +495,144 @@ function selectVideo(video: DemandVideoItem) {
|
|
|
font-weight: 600;
|
|
|
color: #7e22ce;
|
|
|
font-variant-numeric: tabular-nums;
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
-.topic-json {
|
|
|
- margin: 0;
|
|
|
- padding: 10px 12px;
|
|
|
+.topic-body {
|
|
|
+ padding: 10px 12px 12px;
|
|
|
overflow: auto;
|
|
|
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
|
- font-size: 11px;
|
|
|
- line-height: 1.45;
|
|
|
- color: #334155;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.topic-section {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.section-head {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.section-label {
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 700;
|
|
|
+ letter-spacing: 0.02em;
|
|
|
+}
|
|
|
+
|
|
|
+.section-count {
|
|
|
+ min-width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ padding: 0 5px;
|
|
|
+ border-radius: 999px;
|
|
|
+ font-size: 10px;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 18px;
|
|
|
+ text-align: center;
|
|
|
+ font-variant-numeric: tabular-nums;
|
|
|
+}
|
|
|
+
|
|
|
+.accent-inspiration .section-label {
|
|
|
+ color: #c2410c;
|
|
|
+}
|
|
|
+.accent-inspiration .section-count {
|
|
|
+ color: #c2410c;
|
|
|
+ background: #ffedd5;
|
|
|
+}
|
|
|
+.accent-inspiration .point-index {
|
|
|
+ background: #ffedd5;
|
|
|
+ color: #c2410c;
|
|
|
+}
|
|
|
+
|
|
|
+.accent-purpose .section-label {
|
|
|
+ color: #1d4ed8;
|
|
|
+}
|
|
|
+.accent-purpose .section-count {
|
|
|
+ color: #1d4ed8;
|
|
|
+ background: #dbeafe;
|
|
|
+}
|
|
|
+.accent-purpose .point-index {
|
|
|
+ background: #dbeafe;
|
|
|
+ color: #1d4ed8;
|
|
|
+}
|
|
|
+
|
|
|
+.accent-key .section-label {
|
|
|
+ color: #7e22ce;
|
|
|
+}
|
|
|
+.accent-key .section-count {
|
|
|
+ color: #7e22ce;
|
|
|
+ background: #f3e8ff;
|
|
|
+}
|
|
|
+.accent-key .point-index {
|
|
|
+ background: #f3e8ff;
|
|
|
+ color: #7e22ce;
|
|
|
+}
|
|
|
+
|
|
|
+.point-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.point-item {
|
|
|
+ padding: 10px 11px;
|
|
|
+ border-radius: 10px;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #e2e8f0;
|
|
|
+ box-shadow: 0 1px 2px rgba(15, 23, 42, 0.03);
|
|
|
+}
|
|
|
+
|
|
|
+.point-title-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.point-index {
|
|
|
+ flex-shrink: 0;
|
|
|
+ width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ margin-top: 1px;
|
|
|
+ border-radius: 5px;
|
|
|
+ font-size: 10px;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 18px;
|
|
|
+ text-align: center;
|
|
|
+ font-variant-numeric: tabular-nums;
|
|
|
+}
|
|
|
+
|
|
|
+.point-title {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 1.35;
|
|
|
+ color: #0f172a;
|
|
|
+ word-break: break-word;
|
|
|
+}
|
|
|
+
|
|
|
+.point-desc {
|
|
|
+ margin: 6px 0 0;
|
|
|
+ padding-left: 26px;
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 1.55;
|
|
|
+ color: #475569;
|
|
|
white-space: pre-wrap;
|
|
|
word-break: break-word;
|
|
|
}
|
|
|
|
|
|
+.section-empty {
|
|
|
+ padding: 8px 10px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #94a3b8;
|
|
|
+ border: 1px dashed #e2e8f0;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: rgba(255, 255, 255, 0.6);
|
|
|
+}
|
|
|
+
|
|
|
.empty {
|
|
|
padding: 20px 10px;
|
|
|
text-align: center;
|