frontend: add the paper list, document view, and paragraph editor
The 论文 menu now carries the papers themselves, read from a pinia store, so a paper opens straight from the rail; every page that changes the list reloads that store, and a filter box appears once there are more than six. PapersView is the library — search, create, edit, open, single and batch delete. PaperDetailView is the writing surface: it renders the server's document verbatim, with an edit button beside every paragraph (written or not, because content only ever enters a paper through the editor), citation markers numbered in reading order to match the 参考文献 list, and a 切换模板 dialog that previews the impact before re-shaping the document. The paragraph editor shows one paragraph as the paper will read it — every sentence on its own line, in sort order, each editable, each able to carry citations — and writes the paragraph back whole. Whitespace-only lines are dropped on save; a citation with an empty 引用内容 is refused; and 所属段落 moves the paragraph to another position, appended after what is already there.
This commit is contained in:
@@ -11,12 +11,14 @@ import { onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Files, Notebook, Setting } from '@element-plus/icons-vue'
|
||||
|
||||
import { listPapers } from '@/api/papers'
|
||||
import { listSectionFields } from '@/api/sectionFields'
|
||||
import { listTemplates } from '@/api/templates'
|
||||
import AppLogo from '@/components/AppLogo.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const paperCount = ref<number | null>(null)
|
||||
const templateCount = ref<number | null>(null)
|
||||
const fieldCount = ref<number | null>(null)
|
||||
|
||||
@@ -28,10 +30,12 @@ function display(value: number | null): string {
|
||||
onMounted(async () => {
|
||||
// `page_size: 1` because only `total` is used. A failure leaves the count as
|
||||
// an em dash rather than blocking the page.
|
||||
const [templates, fields] = await Promise.allSettled([
|
||||
const [papers, templates, fields] = await Promise.allSettled([
|
||||
listPapers({ page: 1, page_size: 1 }),
|
||||
listTemplates({ page: 1, page_size: 1 }),
|
||||
listSectionFields({ page: 1, page_size: 1 }),
|
||||
])
|
||||
if (papers.status === 'fulfilled') paperCount.value = papers.value.total
|
||||
if (templates.status === 'fulfilled') templateCount.value = templates.value.total
|
||||
if (fields.status === 'fulfilled') fieldCount.value = fields.value.total
|
||||
})
|
||||
@@ -49,8 +53,10 @@ onMounted(async () => {
|
||||
<button type="button" class="entry" @click="router.push('/papers')">
|
||||
<el-icon class="entry-icon"><Notebook /></el-icon>
|
||||
<span class="entry-title">论文</span>
|
||||
<span class="entry-desc">按模板撰写、管理与导出论文</span>
|
||||
<el-tag size="small" type="info" effect="plain">内容待定</el-tag>
|
||||
<span class="entry-desc">按模板逐段撰写与管理论文</span>
|
||||
<el-tag size="small" type="success" effect="plain">
|
||||
{{ display(paperCount) }} 篇
|
||||
</el-tag>
|
||||
</button>
|
||||
|
||||
<button type="button" class="entry" @click="router.push('/templates')">
|
||||
@@ -64,6 +70,14 @@ onMounted(async () => {
|
||||
</section>
|
||||
|
||||
<el-card shadow="never" class="stats">
|
||||
<div class="stat">
|
||||
<el-icon class="stat-icon"><Notebook /></el-icon>
|
||||
<div>
|
||||
<div class="stat-value">{{ display(paperCount) }}</div>
|
||||
<div class="stat-label">论文</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider direction="vertical" class="stat-divider" />
|
||||
<div class="stat">
|
||||
<el-icon class="stat-icon"><Setting /></el-icon>
|
||||
<div>
|
||||
@@ -80,7 +94,8 @@ onMounted(async () => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-hint">
|
||||
字段可以自由组合进模板,显示顺序完全由每个字段的 sort 决定。
|
||||
字段可以自由组合进模板,显示顺序完全由每个字段的 sort 决定;论文按模板的段落逐句填写,
|
||||
换模板不会丢内容。
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user