xiongzhu %!s(int64=2) %!d(string=hai) anos
pai
achega
d9ab7cd8f3
Modificáronse 3 ficheiros con 268 adicións e 92 borrados
  1. 10 2
      src/api/index.ts
  2. 52 36
      src/components/common/OutlineEditor.vue
  3. 206 54
      src/views/page/PaperGen.vue

+ 10 - 2
src/api/index.ts

@@ -236,9 +236,10 @@ export function fetchPaperOrders<T>(data: any) {
     })
 }
 
-export function fetchGenPaper<T>(id: number) {
+export function fetchGenPaper<T>(id: number, chapters?: any) {
     return post<T>({
-        url: `/paper/orders/${id}/gen`
+        url: `/paper/orders/${id}/gen`,
+        data: chapters
     })
 }
 
@@ -262,3 +263,10 @@ export function fetchUpdatePaperOrder<T>(id: number, data: any) {
         data
     })
 }
+
+export function fetchGenChapters<T>(data: { major: string; title: string; description: string }) {
+    return post<T>({
+        url: `/paper/orders/genChapters`,
+        data
+    })
+}

+ 52 - 36
src/components/common/OutlineEditor.vue

@@ -1,21 +1,22 @@
 <template>
-    <NElement
-        tag="div"
-        class="et-outline m-4 p-2 rounded-lg border outline-none text-left overflow-auto"
-        @keydown="onKeyDown"
-        @keypress="onKeyPressed"
-        @keyup="onKeyUp"
-        contenteditable
-        style="width: 200px; height: 300px"
-        ref="el"
-    >
-        <p data-level="0" data-title="第一章"></p>
-    </NElement>
+    <div class="w-full">
+        <NElement
+            tag="div"
+            class="et-outline p-2 rounded border outline-none text-left overflow-auto transition-all duration-300"
+            @keydown="onKeyDown"
+            @keypress="onKeyPressed"
+            @keyup="onKeyUp"
+            contenteditable
+            ref="el"
+        >
+            <p data-level="0" data-title="第一章"></p>
+        </NElement>
+    </div>
 </template>
 <script setup lang="ts">
 import { Ref, computed, onMounted, reactive, ref, watch, defineEmits } from 'vue'
 import { useMutationObserver, useDebounceFn } from '@vueuse/core'
-import { NConfigProvider, NElement, useThemeVars } from 'naive-ui'
+import { NButton, NConfigProvider, NElement, useThemeVars } from 'naive-ui'
 // @ts-ignore
 import * as NumberToChinese from '@siakhooi/number-to-chinese-words'
 
@@ -25,11 +26,15 @@ const props = defineProps({
         default: (): any[] => []
     }
 })
-watch(props.modelValue, v => {
-    if (JSON.stringify(v) !== JSON.stringify(props.modelValue)) {
-        update(props.modelValue)
+watch(
+    () => props.modelValue,
+    (v, ov) => {
+        console.log('watch', v)
+        if (JSON.stringify(v) !== JSON.stringify(chapters)) {
+            update(props.modelValue)
+        }
     }
-})
+)
 const themeVars = useThemeVars()
 const el: Ref<any> = ref(null)
 const et = computed(() => {
@@ -37,15 +42,17 @@ const et = computed(() => {
 })
 
 onMounted(() => {
+    console.log('outline editor mounted')
     useMutationObserver(
         et.value,
         mutations => {
+            console.log(mutations)
             if (mutations.flatMap(m => Array.from(m.addedNodes).concat(Array.from(m.removedNodes))).length > 0) {
                 arrange()
             } else if (mutations.filter(m => m.type === 'characterData').length > 0) {
                 arrange()
             }
-            // arrange()
+            arrange()
         },
         {
             attributes: true,
@@ -64,6 +71,7 @@ watch(chapters, v => {
     emit('update:modelValue', v)
 })
 const arrange = useDebounceFn(() => {
+    console.log('arrange')
     const levels = [0, 0, 0, 0]
     chapters.splice(0, chapters.length)
     for (const child of Array.from(et.value?.children || [])) {
@@ -73,14 +81,13 @@ const arrange = useDebounceFn(() => {
         for (let i = level + 1; i < levels.length; i++) {
             levels[i] = 0
         }
+        let title
         if (level === 0) {
-            e.setAttribute(
-                'data-title',
-                '第' + NumberToChinese.convertNumber(levels[0], { removeLeadingOne: levels[level] > 1 }) + '章'
-            )
+            title = '第' + NumberToChinese.convertNumber(levels[0], { removeLeadingOne: levels[level] > 1 }) + '章'
         } else {
-            e.setAttribute('data-title', levels.slice(0, level + 1).join('.'))
+            title = levels.slice(0, level + 1).join('.')
         }
+        if (e.getAttribute('data-title') !== title) e.setAttribute('data-title', title)
 
         let node: any
         let parent = chapters
@@ -96,11 +103,17 @@ const arrange = useDebounceFn(() => {
         })
         if (node) node.title = e.textContent
     }
-}, 10)
+    setTimeout(() => {
+        emit('update:modelValue', chapters)
+    }, 1000)
+}, 100)
 
 function update(chaptersData: any) {
-    console.log('update')
-    chaptersData = chaptersData || []
+    console.log('update', chaptersData)
+    chaptersData = chaptersData || [{ title: '', level: 0 }]
+    if (chaptersData.length === 0) {
+        chaptersData.push({ title: '', level: 0 })
+    }
     const list: any[] = []
     function _update(_data: any, level: number) {
         _data.forEach((e: any) => {
@@ -108,19 +121,19 @@ function update(chaptersData: any) {
                 title: e.title,
                 level
             })
-            _update(e.children, level + 1)
+            if (e.children) _update(e.children, level + 1)
         })
     }
     _update(chaptersData, 0)
-    et.value?.childNodes.forEach((e: any) => {
-        et.value?.removeChild(e)
-    })
-    list.forEach((e, i) => {
-        const p = document.createElement('p')
-        p.setAttribute('data-level', e.level.toString())
-        p.textContent = e.title
-        et.value?.appendChild(p)
-    })
+    et.value?.replaceChildren(
+        ...list.map(e => {
+            const p = document.createElement('p')
+            p.setAttribute('data-level', e.level.toString())
+            p.textContent = e.title
+            return p
+        })
+    )
+
     arrange()
     console.log(list)
 }
@@ -174,6 +187,9 @@ function onKeyUp(e: KeyboardEvent) {}
 </script>
 <style lang="less" scoped>
 .et-outline {
+    border-color: var(--border-color);
+    min-height: 488px;
+    max-height: 488px;
     &:focus {
         border-color: var(--primary-color);
     }

+ 206 - 54
src/views/page/PaperGen.vue

@@ -1,5 +1,4 @@
 <template>
-    <OutlineEditor v-model="chapters" />
     <NLayout class="h-full">
         <NLayoutHeader class="flex items-center px-8" style="height: 64px">
             <span class="text-lg flex-1">论文助手</span>
@@ -46,7 +45,7 @@
     </n-modal>
     <n-modal v-model:show="showForm">
         <n-card
-            style="width: 600px"
+            style="width: 800px"
             :title="orderId ? '编辑' : '创建'"
             :bordered="false"
             size="huge"
@@ -54,18 +53,40 @@
             aria-modal="true"
         >
             <NForm ref="form" :model="model" :rules="rules">
-                <NFormItem label="专业" path="major">
-                    <NInput v-model:value="model.major" />
-                </NFormItem>
-                <NFormItem label="标题" path="title">
-                    <NInput v-model:value="model.title" />
-                </NFormItem>
-                <NFormItem label="描述" path="description">
-                    <NInput type="textarea" v-model:value="model.description" />
-                </NFormItem>
-                <NFormItem label="备注" path="remark">
-                    <NInput v-model:value="model.remark" />
-                </NFormItem>
+                <div class="flex">
+                    <div class="flex-1">
+                        <NFormItem label="专业" path="major">
+                            <NInput v-model:value="model.major" />
+                        </NFormItem>
+                        <NFormItem label="标题" path="title">
+                            <NInput v-model:value="model.title" />
+                        </NFormItem>
+                        <NFormItem label="描述" path="description">
+                            <NInput type="textarea" v-model:value="model.description" :resizable="false" :rows="10" />
+                        </NFormItem>
+                        <NFormItem label="备注" path="remark">
+                            <NInput v-model:value="model.remark" />
+                        </NFormItem>
+                    </div>
+                    <div class="flex-1 ml-4">
+                        <NFormItem label="大纲" path="chapters">
+                            <div class="w-full">
+                                <OutlineEditor v-model="model.chapters" />
+                                <div class="flex mt-1 items-center">
+                                    <NButton
+                                        size="small"
+                                        quaternary
+                                        type="primary"
+                                        :loading="genChaptering"
+                                        @click="genChapters"
+                                        >生成大纲</NButton
+                                    >
+                                    <div class="flex-1 text-sm text-right">Tab / Shift + Tab 切换级别</div>
+                                </div>
+                            </div>
+                        </NFormItem>
+                    </div>
+                </div>
             </NForm>
             <NSpace class="text-right" justify="end">
                 <NButton @click="() => (showForm = false)">取消</NButton>
@@ -116,7 +137,14 @@ import {
     NDrawer,
     NDrawerContent
 } from 'naive-ui'
-import { fetchPaperOrders, fetchGenPaper, fetchPaperResults, fetchCreatePaperOrder, fetchUpdatePaperOrder } from '@/api'
+import {
+    fetchPaperOrders,
+    fetchGenPaper,
+    fetchPaperResults,
+    fetchCreatePaperOrder,
+    fetchUpdatePaperOrder,
+    fetchGenChapters
+} from '@/api'
 import { format, parseISO } from 'date-fns'
 import { UserAvatar } from '@/components/common'
 import { LoginForm } from '@/components/common'
@@ -420,7 +448,8 @@ const model = ref({
     major: '',
     title: '',
     description: '',
-    remark: ''
+    remark: '',
+    chapters: []
 })
 const rules = {
     major: [{ required: true, message: '请输入专业' }],
@@ -434,7 +463,8 @@ function onCreate() {
         major: '',
         title: '',
         description: '',
-        remark: ''
+        remark: '',
+        chapters: []
     }
     form.value?.restoreValidation()
     showForm.value = true
@@ -463,33 +493,14 @@ function editRow(row: any) {
         major: row.major,
         title: row.title,
         description: row.description,
-        remark: row.remark
+        remark: row.remark,
+        chapters: row.chapters || []
     }
     orderId.value = row.id
     form.value?.restoreValidation()
     showForm.value = true
 }
 
-const mdi = new MarkdownIt({
-    linkify: true,
-    highlight(code, language) {
-        const validLang = !!(language && hljs.getLanguage(language))
-        if (validLang) {
-            const lang = language ?? ''
-            return highlightBlock(hljs.highlight(code, { language: lang }).value, lang)
-        }
-        return highlightBlock(hljs.highlightAuto(code).value, '')
-    }
-})
-
-mdi.use(mila, { attrs: { target: '_blank', rel: 'noopener' } })
-mdi.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' })
-function highlightBlock(str: string, lang?: string) {
-    return `<pre class="code-block-wrapper"><div class="code-block-header"><span class="code-block-header__lang">${lang}</span><span class="code-block-header__copy">${t(
-        'chat.copyCode'
-    )}</span></div><code class="hljs code-block-body ${lang}">${str}</code></pre>`
-}
-
 const showEditor = ref(false)
 function onEditorShow() {
     console.log('show')
@@ -498,21 +509,162 @@ function onEditorShow() {
     })
 }
 
-const chapters: Ref<any[]> = ref([
-    { children: [], title: 'asdf' },
-    { children: [], title: 'asdf' },
-    {
-        children: [
-            { children: [], title: 'werwefwsdfasdf' },
-            {
-                children: [
-                    { children: [], title: 'sdf' },
-                    { children: [], title: '' }
-                ],
-                title: 'asdf'
-            }
-        ],
-        title: 'asdf'
+const genChaptering = ref(false)
+async function genChapters() {
+    await form.value?.validate()
+    try {
+        genChaptering.value = true
+        const chapters = await fetchGenChapters({
+            major: model.value.major,
+            title: model.value.title,
+            description: model.value.description
+        })
+        // const chapters = [
+        //     {
+        //         chapterName: '引言',
+        //         chapterDesc: '介绍论文的研究背景、动机和目的',
+        //         sections: [
+        //             {
+        //                 sectionName: '研究背景',
+        //                 sectionDesc: '阐述电器销售系统的重要性和发展现状'
+        //             },
+        //             {
+        //                 sectionName: '研究动机',
+        //                 sectionDesc: '说明为什么选择基于Java开发电器销售系统'
+        //             },
+        //             {
+        //                 sectionName: '研究目的和意义',
+        //                 sectionDesc: '明确研究目标和对电器销售领域的贡献'
+        //             }
+        //         ]
+        //     },
+        //     {
+        //         chapterName: '文献综述',
+        //         chapterDesc: '对电器销售系统及相关技术和工具进行综述',
+        //         sections: [
+        //             {
+        //                 sectionName: '电器销售系统现状',
+        //                 sectionDesc: '分析电器销售系统的发展趋势和市场需求'
+        //             },
+        //             {
+        //                 sectionName: '相关技术和工具',
+        //                 sectionDesc: '介绍与Java开发电器销售系统相关的技术和工具'
+        //             }
+        //         ]
+        //     },
+        //     {
+        //         chapterName: '系统需求分析与设计',
+        //         chapterDesc: '分析电器销售系统的功能和非功能需求,并进行系统架构设计',
+        //         sections: [
+        //             {
+        //                 sectionName: '功能需求',
+        //                 sectionDesc: '明确前台销售和后台管理的功能要求'
+        //             },
+        //             {
+        //                 sectionName: '非功能需求',
+        //                 sectionDesc: '阐述性能、安全、可维护性和用户界面设计的要求'
+        //             },
+        //             {
+        //                 sectionName: '系统架构设计',
+        //                 sectionDesc: '设计系统的前端、后端和数据库架构'
+        //             }
+        //         ]
+        //     },
+        //     {
+        //         chapterName: '实现与测试',
+        //         chapterDesc: '具体实现电器销售系统的功能模块,并进行系统测试',
+        //         sections: [
+        //             {
+        //                 sectionName: '开发环境和工具选择',
+        //                 sectionDesc: '选择合适的开发环境和工具进行系统实现'
+        //             },
+        //             {
+        //                 sectionName: '功能模块实现和集成',
+        //                 sectionDesc: '详细描述各个功能模块的实现,并进行集成测试'
+        //             },
+        //             {
+        //                 sectionName: '系统测试策略和方法',
+        //                 sectionDesc: '说明系统测试的策略和方法,确保系统质量'
+        //             },
+        //             {
+        //                 sectionName: '测试结果分析和评估',
+        //                 sectionDesc: '分析测试结果并评估系统的性能和功能完整性'
+        //             }
+        //         ]
+        //     },
+        //     {
+        //         chapterName: '性能优化与安全保障',
+        //         chapterDesc: '对电器销售系统进行性能优化和安全保障措施',
+        //         sections: [
+        //             {
+        //                 sectionName: '性能评估和优化策略',
+        //                 sectionDesc: '评估系统性能,并提出性能优化策略'
+        //             },
+        //             {
+        //                 sectionName: '安全风险识别与防护技术',
+        //                 sectionDesc: '分析系统的安全风险并提出相应的防护技术'
+        //             }
+        //         ]
+        //     },
+        //     {
+        //         chapterName: '结果与讨论',
+        //         chapterDesc: '分析系统实现的功能和性能结果,并讨论用户反馈与建议',
+        //         sections: [
+        //             {
+        //                 sectionName: '系统功能与性能结果分析',
+        //                 sectionDesc: '对系统实现的功能和性能进行详细分析'
+        //             },
+        //             {
+        //                 sectionName: '用户反馈与建议',
+        //                 sectionDesc: '收集用户的反馈和建议,并进行讨论和改进'
+        //             }
+        //         ]
+        //     },
+        //     {
+        //         chapterName: '总结与展望',
+        //         chapterDesc: '对论文进行总结,并展望未来的研究方向和改进建议',
+        //         sections: [
+        //             {
+        //                 sectionName: '主要研究成果总结',
+        //                 sectionDesc: '总结论文取得的主要研究成果'
+        //             },
+        //             {
+        //                 sectionName: '存在的问题和不足',
+        //                 sectionDesc: '概述论文中存在的问题和不足之处'
+        //             },
+        //             {
+        //                 sectionName: '后续研究方向和改进建议',
+        //                 sectionDesc: '提出未来的研究方向和改进建议'
+        //             }
+        //         ]
+        //     }
+        // ]
+        formatChapters(chapters)
+        console.log(chapters)
+        model.value.chapters = chapters as any
+    } catch (error: any) {
+        message.error(error.message)
     }
-])
+    genChaptering.value = false
+}
+function formatChapters(chapters: any) {
+    chapters.forEach((item: any) => {
+        if (item.chapterName) {
+            item.title = item.chapterName
+        } else {
+            item.title = item.sectionName
+        }
+        if (item.sections) {
+            item.children = item.sections
+        }
+        delete item.sections
+        delete item.chapterName
+        delete item.chapterDesc
+        delete item.sectionName
+        delete item.sectionDesc
+        if (item.children) {
+            formatChapters(item.children)
+        }
+    })
+}
 </script>