Skip to content

Commit b7ef5a3

Browse files
committed
add writer sequ
1 parent fbc6b66 commit b7ef5a3

File tree

5 files changed

+460
-18
lines changed

5 files changed

+460
-18
lines changed

frontend/src/apis/commonApi.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,8 @@ import request from "@/utils/request";
33
export function getHelloWorld() {
44
return request.get<{ message: string }>("/");
55
}
6+
7+
// 获取论文顺序
8+
export function getWriterSeque() {
9+
return request.get<{ writer_seque: string[] }>("/writer_seque");
10+
}

frontend/src/components/WriterEditor.vue

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,58 @@
11
<script setup lang="ts">
2-
import { ref, watch } from 'vue';
2+
import { onMounted, ref, watch, computed } from 'vue';
33
import { renderMarkdown } from '@/utils/markdown';
44
import type { WriterMessage } from '@/utils/response'
5+
import { ScrollArea } from '@/components/ui/scroll-area'
6+
import { getWriterSeque } from '@/apis/commonApi';
57
68
interface ContentSection {
79
id: number;
810
content: string;
911
renderedContent: string;
12+
sub_title?: string;
1013
}
1114
1215
const props = defineProps<{
1316
messages: WriterMessage[]
1417
}>()
1518
19+
const writerSequence = ref<string[]>([]);
20+
21+
onMounted(async () => {
22+
const res = await getWriterSeque();
23+
writerSequence.value = Array.isArray(res.data) ? res.data : [];
24+
});
25+
1626
const sections = ref<ContentSection[]>([]);
1727
let nextId = 0;
1828
1929
// 添加新的内容段落
20-
const appendContent = async (content: string) => {
30+
const appendContent = async (content: string, sub_title?: string) => {
2131
const renderedContent = await renderMarkdown(content);
2232
sections.value.push({
2333
id: nextId++,
2434
content,
25-
renderedContent
35+
renderedContent,
36+
sub_title
2637
});
2738
};
2839
40+
// 根据 writerSequence 排序内容
41+
const sortedSections = computed(() => {
42+
if (!writerSequence.value.length) return sections.value;
43+
44+
return [...sections.value].sort((a, b) => {
45+
const aIndex = a.sub_title ? writerSequence.value.indexOf(a.sub_title) : Infinity;
46+
const bIndex = b.sub_title ? writerSequence.value.indexOf(b.sub_title) : Infinity;
47+
48+
if (aIndex === Infinity && bIndex === Infinity) return 0;
49+
if (aIndex === Infinity) return 1;
50+
if (bIndex === Infinity) return -1;
51+
52+
return aIndex - bIndex;
53+
});
54+
});
55+
2956
// 监听消息变化
3057
watch(() => props.messages, async (messages) => {
3158
// 清空现有内容
@@ -35,27 +62,25 @@ watch(() => props.messages, async (messages) => {
3562
// 按顺序添加每个消息的内容
3663
for (const msg of messages) {
3764
if (msg.content) {
38-
await appendContent(msg.content);
65+
await appendContent(msg.content, msg.sub_title);
3966
}
4067
}
4168
}, { immediate: true });
4269
</script>
4370

4471
<template>
45-
<div class="h-full overflow-hidden">
46-
<div class="h-full overflow-y-auto p-6">
47-
<div class="max-w-4xl mx-auto space-y-6">
48-
<TransitionGroup name="section" tag="div" class="space-y-6">
49-
<div v-for="section in sections" :key="section.id"
50-
class="bg-white rounded-lg shadow-lg overflow-hidden transform transition-all duration-500">
51-
<div class="p-6">
52-
<div class="prose prose-slate max-w-none" v-html="section.renderedContent"></div>
53-
</div>
72+
<ScrollArea class="h-full overflow-y-auto p-6">
73+
<div class="max-w-4xl mx-auto space-y-6">
74+
<TransitionGroup name="section" tag="div" class="space-y-6">
75+
<div v-for="section in sortedSections" :key="section.id"
76+
class="bg-white rounded-lg shadow-lg overflow-hidden transform transition-all duration-500">
77+
<div class="p-6">
78+
<div class="prose prose-slate max-w-none" v-html="section.renderedContent"></div>
5479
</div>
55-
</TransitionGroup>
56-
</div>
80+
</div>
81+
</TransitionGroup>
5782
</div>
58-
</div>
83+
</ScrollArea>
5984
</template>
6085

6186
<style>

frontend/src/stores/task.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
22
import { ref, computed } from 'vue'
33
import { TaskWebSocket } from '@/utils/websocket'
44
import type { Message, CoderMessage, WriterMessage } from '@/utils/response'
5-
import messageData from '@/test/20250429-192632-60df0e49.json'
5+
import messageData from '@/test/20250430-163507-03282e09.json'
66

77
export const useTaskStore = defineStore('task', () => {
88
// 初始化时直接加载测试数据,确保页面首次渲染时有数据

frontend/src/test/20250430-163507-03282e09.json

Lines changed: 381 additions & 0 deletions
Large diffs are not rendered by default.

frontend/src/utils/markdown.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { marked } from 'marked'
22
import katex from 'katex'
3-
import type { RendererObject, Renderer } from 'marked'
3+
import type { RendererObject, Renderer, Token } from 'marked'
44

55
// 默认的markdown渲染配置
66
const defaultOptions = {
@@ -30,6 +30,15 @@ const renderer: Partial<RendererObject> = {
3030
paragraph(this: Renderer, token: { text: string }) {
3131
let text = token.text
3232

33+
// 先处理图片链接,避免被误识别为数学公式
34+
const imagePattern = /!\[(.*?)\]\((.*?)\)/g
35+
const images: Array<[string, string, string]> = []
36+
let imageIndex = 0
37+
text = text.replace(imagePattern, (match, alt, src) => {
38+
images.push([match, alt, src])
39+
return `__IMAGE_PLACEHOLDER_${imageIndex++}__`
40+
})
41+
3342
// 处理块级公式
3443
if (text.startsWith('\\[') && text.endsWith('\\]')) {
3544
const tex = text.slice(2, -2).trim()
@@ -50,13 +59,35 @@ const renderer: Partial<RendererObject> = {
5059
})
5160
}
5261

62+
// 还原图片占位符
63+
text = text.replace(/__IMAGE_PLACEHOLDER_(\d+)__/g, (_, index) => {
64+
const [, alt, src] = images[parseInt(index)]
65+
return `<img src="${src}" alt="${alt}" class="max-w-full h-auto" />`
66+
})
67+
5368
return `<p>${text}</p>`
5469
}
5570
}
5671

5772
// 配置marked
5873
marked.use({ renderer })
5974

75+
// 配置图片处理
76+
marked.use({
77+
hooks: {
78+
preprocess(markdown) {
79+
// 处理图片链接
80+
const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000'
81+
const taskId = window.localStorage.getItem('currentTaskId') || ''
82+
83+
return markdown.replace(
84+
/!\[(.*?)\]\(((?!http[s]?:\/\/).*?\.(?:png|jpg|jpeg|gif|bmp|webp))\)/g,
85+
(_, alt, src) => `![${alt}](${baseUrl}/static/${taskId}/${src})`
86+
)
87+
}
88+
}
89+
})
90+
6091
/**
6192
* 渲染Markdown文本为HTML
6293
* @param content Markdown文本

0 commit comments

Comments
 (0)